简体   繁体   English

不同 DbContext 和不同模式之间的实体框架关系

[英]Entity Framework relationships between different DbContext and different schemas

So, I have two main objects, Member and Guild.所以,我有两个主要对象,Member 和 Guild。 One Member can own a Guild and one Guild can have multiple Members.一个会员可以拥有一个公会,一个公会可以拥有多个会员。

I have the Members class in a separate DbContext and separate class library.我在单独的 DbContext 和单独的类库中有成员类。 I plan to reuse this class library in multiple projects and to help differentiate, I set the database schema to be "acc".我计划在多个项目中重用这个类库并帮助区分,我将数据库模式设置为“acc”。 I have tested this library extensively and can add, delete, and update Members in the acc.Members table.我对这个库进行了广泛的测试,可以添加、删除和更新 acc.Members 表中的成员。

The Guild class is as such: Guild 类是这样的:

public class Guild
{
    public Guild()
    {
        Members = new List<Member>();
    }

    public int ID { get; set; }
    public int MemberID { get; set; }
    public virtual Member LeaderMemberInfo { get; set; }
    public string Name { get; set; }
    public virtual List<Member> Members { get; set; }
}

with a mapping of:映射为:

internal class GuildMapping : EntityTypeConfiguration<Guild>
{
    public GuildMapping()
    {
        this.ToTable("Guilds", "dbo");
        this.HasKey(t => t.ID);
        this.Property(t => t.MemberID);
        this.HasRequired(t => t.LeaderMemberInfo).WithMany().HasForeignKey(t => t.MemberID);
        this.Property(t => t.Name);
        this.HasMany(t => t.Members).WithMany()
            .Map(t =>
            {
                t.ToTable("GuildsMembers", "dbo");
                t.MapLeftKey("GuildID");
                t.MapRightKey("MemberID");
            });
    }
}

But, when I try to create a new Guild, it says that there is no dbo.Members.但是,当我尝试创建一个新的公会时,它说没有 dbo.Members。

I got reference to the Member's EF project and added the mapping to the Members class to the DbContext that the Guild class is a part of.我参考了 Member 的 EF 项目,并将对成员类的映射添加到 Guild 类所属的 DbContext。 modelBuilder.Configurations.Add(new MemberMapping()); (Not sure if that is the best way.) (不确定这是否是最好的方法。)

This resulted with this error:这导致了这个错误:

{"The member with identity 'GuildProj.Data.EF.Guild_Members' does not exist in the metadata collection.\r\nParameter name: identity"}

How can I utilize the foreign key between these two tables cross DbContexts and with different database schemas?如何跨 DbContext 和不同的数据库模式使用这两个表之间的外键?

UPDATE更新

I narrowed down the cause of the error.我缩小了错误的原因。 When I create a new guild, I set the guild leader's Member ID to MemberID.当我创建一个新的公会时,我将公会领导的Member ID 设置为MemberID。 This works fine.这工作正常。 But, when I then try to add that leader's Member object to the Guild's List of Members (Members), that's what causes the error.但是,当我然后尝试将该领导者的 Member 对象添加到 Guild's List of Members (Members) 时,这就是导致错误的原因。

UPDATE 2更新 2

Here is the code of how I create the Context that the Guild class is in. (As requested by Hussein Khalil)这是我如何创建 Guild 类所在的 Context 的代码。(根据 Hussein Khalil 的要求)

public class FSEntities : DbContext
{
    public FSEntities()
    {
        this.Configuration.LazyLoadingEnabled = false;
        Database.SetInitializer<FSEntities>(null);
    }

    public FSEntities(string connectionString)
        : base(connectionString)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new GuildMapping());
        modelBuilder.Configurations.Add(new KeyValueMappings());
        modelBuilder.Configurations.Add(new LocaleMappings());

        modelBuilder.Configurations.Add(new MemberMapping());
    }

    public DbSet<Guild> Guilds { get; set; }
    public DbSet<KeyValue> KeyValues { get; set; }
    public DbSet<Locale> Locales { get; set; }
}

This is how I am saving it in the repo:这就是我在回购中保存它的方式:

    public async Task CreateGuildAsync(Guild guild)
    {
        using (var context = new FSEntities(_ConnectionString))
        {
            context.Entry(guild.Members).State = EntityState.Unchanged;
            context.Entry(guild).State = EntityState.Added;
            await context.SaveChangesAsync();
        }
    }

FINAL RESOLUTION最终决议

So, I had to add mappings to Member , Role , and Permission in DbContext that contained Guild .因此,我必须在包含Guild DbContext 中添加到MemberRolePermission映射。 I had to add Role and Permission because Member had List<Role> Roles and each Role had List<Permission> Permissions .我必须添加 Role 和 Permission 因为 Member 有List<Role> Roles并且每个 Role 都有List<Permission> Permissions

This got me closer to the solution.这让我更接近解决方案。 I was still getting errors like:我仍然收到如下错误:

{"The member with identity 'GuildProj.Data.EF.Member_Roles' does not exist in the metadata collection.\r\nParameter name: identity"}

Here, when you pull Member from the Session , you get something like this:在这里,当您从Session拉出 Member 时,您会得到如下内容:

System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C

Entity Framework does not seem to play well with this.实体框架似乎并不能很好地解决这个问题。 Why?为什么? I am not sure, but I think it is because ContextM creates a proxy of Member and by cloning the Member into a new Member object, ContextM no longer has association.我不确定,但我认为这是因为 ContextM 创建了 Member 的代理,并且通过将 Member 克隆到新的 Member 对象中,ContextM 不再具有关联。 This, I think, allows ContextG to use the new Member object freely.我认为,这允许 ContextG 自由使用新的 Member 对象。 I tried setting ProxyCreationEnabled = false in my DbContexts, but the Member object being pulled out of Session kept being of type System.Data.Entity.DynamicProxies.Member.我尝试在我的 DbContexts 中设置 ProxyCreationEnabled = false,但是从 Session 中拉出的 Member 对象一直是 System.Data.Entity.DynamicProxies.Member 类型。

So, what I did was:所以,我所做的是:

Member member = new Member((Member)Session[Constants.UserSession]);

I had to clone each Role and each Permission as well inside their respective constructors.我必须在它们各自的构造函数中克隆每个Role和每个Permission

This got me 99% of the way there.这让我完成了 99% 的工作。 I had to alter my repo and how I was saving the Guild object.我不得不改变我的 repo 以及我如何保存Guild对象。

            context.Entry(guild.LeaderMemberInfo).State = EntityState.Unchanged;
            foreach(var member in guild.Members)
            {
                context.Entry(member).State = EntityState.Unchanged;
            }
            context.Entry(guild).State = EntityState.Added;
            await context.SaveChangesAsync();

This is working code: 这是工作代码:

In assembly "M": 在装配“M”中:

public class Member
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MemberMapping : EntityTypeConfiguration<Member>
{
    public MemberMapping()
    {
        this.HasKey(m => m.Id);
        this.Property(m => m.Name).IsRequired();
    }
}

In assemby "G": 在组合“G”中:

  • your Guild class 你的Guild
  • your Guild mapping, albeit with WillCascadeOnDelete(false) in the LeaderMemberInfo mapping. 您的Guild映射,尽管在LeaderMemberInfo映射中使用了WillCascadeOnDelete(false)
  • modelBuilder.Configurations.Add(new GuildMapping()); and modelBuilder.Configurations.Add(new MemberMapping()); modelBuilder.Configurations.Add(new MemberMapping());

Code: 码:

var m = new Member { Name = "m1" };
var lm = new Member { Name = "leader" };
var g = new Guild { Name = "g1" };
g.LeaderMemberInfo = lm;
g.Members.Add(lm);
g.Members.Add(m);
c.Set<Guild>().Add(g);
c.SaveChanges();

Executed SQL: 执行SQL:

INSERT [dbo].[Members]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[Members]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'leader' (Type = String, Size = -1)

INSERT [dbo].[Guilds]([MemberID], [Name])
VALUES (@0, @1)
SELECT [ID]
FROM [dbo].[Guilds]
WHERE @@ROWCOUNT > 0 AND [ID] = scope_identity()
-- @0: '1' (Type = Int32)
-- @1: 'g1' (Type = String, Size = -1)

INSERT [dbo].[GuildsMembers]([GuildID], [MemberID])
VALUES (@0, @1)
-- @0: '1' (Type = Int32)
-- @1: '1' (Type = Int32)

INSERT [dbo].[Members]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[Members]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'm1' (Type = String, Size = -1)

INSERT [dbo].[GuildsMembers]([GuildID], [MemberID])
VALUES (@0, @1)
-- @0: '1' (Type = Int32)
-- @1: '2' (Type = Int32)

This also works when associating existing objects. 这在关联现有对象时也有效。


Original answer for more general case: 更一般情况的原始答案:

You can't combine types in different contexts into one object graph. 您不能将不同上下文中的类型组合到一个对象图中。 That means, you can't do something like 这意味着,你不能做类似的事情

from a in context.As
join b in context.Bs on ...

...because there's always one context that should create the whole SQL query, so it should have all required mapping information. ...因为总有一个上下文应该创建整个SQL查询,所以它应该具有所有必需的映射信息。

You can register the same type into two different contexts though, even from different assemblies. 您可以将相同类型注册到两个不同的上下文中,即使是来自不同的程序集也是如此。 So you could map Member in the context in Guild 's assembly, let's call it contextG , but only if 所以你可以在Guild的程序集中的上下文中映射Member ,让我们称它为contextG ,但仅限于

  1. Member doesn't refer to other types that aren't mapped in contextG . Member不引用contextG映射的其他类型。 This may imply that navigation properties in Member must be ignored explicitly. 这可能意味着必须明确忽略Member中的导航属性。
  2. Member can't refer to types in contextG , because these types are not part of Member 's context. Member不能引用contextG类型,因为这些类型不是Member上下文的一部分。

If any of these conditions can't be fulfilled the best you can do is create a new Member class in Guild 's assembly and register its mapping in the context. 如果无法满足任何这些条件,您可以做的最好的事情是在Guild的程序集中创建一个新的Member类,并在上下文中注册它的映射。 Maybe you want to use a different name to prevent ambiguity, but this is about the only alternative left. 也许您想使用不同的名称来防止歧义,但这是唯一的替代选择。

I have found that when I am having problem with Entity and building relationships it's often because I am going against the flow of the framework or trying to build relationships or abstractions that are not technically well-formed. 我发现当我遇到Entity问题并建立关系时,通常是因为我反对框架的流程或试图建立技术上不完善的关系或抽象。 What I would suggest here is to take a quick step back and analyze a few things before looking deeper into this specific issue. 我在这里建议的是快速退一步并分析一些事情,然后再深入研究这个具体问题。

First off, I am curious why you are using different schema here for what is likely a single application accessing an object graph. 首先,我很好奇为什么你在这里使用不同的模式来获取访问对象图的单个应用程序。 Multiple schemas can be useful in some context but I think Brent Ozar makes a very salient point in this article . 在某些情况下,多个模式可能很有用,但我认为Brent Ozar在本文中提出了一个非常突出的观点。 Given that information, I am inclined to first suggest that you merge the multiple schema into one and push over to using a single DB context for the database. 鉴于这些信息,我倾向于首先建议您将多个模式合并为一个并推送到数据库使用单个数据库上下文。

The next thing to tackle is the object graph. 接下来要解决的是对象图。 At least for me the biggest struggles I have had with data modeling are when I don't first figure out what questions the application has of the database. 至少对我而言,我在数据建模方面遇到的最大困难是,当我不首先弄清楚应用程序对数据库有什么问题时。 What I mean by this is figure out what data does the application want in its various contexts first and then look at how to optimize those data structures for performance in a relational context. 我的意思是首先弄清楚应用程序在各种上下文中想要的数据,然后看看如何在关系上下文中优化这些数据结构的性能。 Let's see how that might be done ... 让我们看看如何做到这一点......

Based on your model above I can see that we have a few key terms/objects in the domain: 根据您上面的模型,我可以看到域中有一些关键术语/对象:

  • Collection of Guilds 公会的集合
  • Collection of Members 会员集
  • Collection of Guild Leaders. 公会领袖的集合。

Additionally we have some business rules that need to be implemented: 此外,我们还有一些需要实施的业务规则:

  • A Guild can have 1 leader (and possibly more than 1?) 公会可以有1个领导者(可能超过1个?)
  • A Guild Leader must be a Member 公会领袖必须是会员
  • A Guild has a list of 0 or more Members 公会有一个0或更多成员的列表
  • A Member can belong to a Guild (and possibly more than 1?) 会员可以属于公会(可能超过1个?)

So given this information let's investigate what questions your application might have of this data model. 因此,鉴于此信息,我们将调查您的应用程序可能对此数据模型提出的问题。 I the application can: 我的申请可以:

  • look up a member and see their properties 查找成员并查看其属性
  • look up a member and see their properties and that they are a guild leader 查找成员并查看他们的属性,他们是公会领导者
  • look up a guild and see a list of all its members 查找一个公会并查看其所有成员的列表
  • look up a guild and see a list of guild leaders 查找一个公会并查看公会领袖名单
  • look up a list of all guild leaders 查看所有公会领袖的名单

Ok, now we can get down to brass tacks, as they say... 好吧,现在我们可以说到正如他们所说的那样......

The use of the join table between Guilds and Members is optimal in this instance. 在这种情况下,在公会和成员之间使用联接表是最佳的。 It will provide you the ability to have members in multiple guilds or no guilds and provide a low locking update strategy - so good call! 它将为您提供在多个公会中拥有成员或没有公会的能力,并提供低锁定更新策略 - 这么好的通话!

Moving onto Guild Leaders there are a few choices that might make sense. 转移到公会领袖,有一些选择可能有意义。 Even though there may never be a case for say guild sergeants, I think it makes sense to consider a new entity called a Guild Leader. 虽然可能永远不会有公会军士的情况,但我认为考虑一个名为公会领袖的新实体是有道理的。 What this approach allows for is several fold. 这种方法允许的是几个方面。 You can cache in app the list of guild leader ids, so rather than make a db trip to authorize a guild action taken by a leader you can hit local application cache that only has the leader id list and not the whole leader object; 您可以在应用程序中缓存公会领导者ID列表,因此,您可以点击只有领导者ID列表而不是整个领导者对象的本地应用程序缓存,而不是通过db行程来授权领导者采取的公会行动。 conversely, you can get the list of leaders for a guild and regardless of the query direction you can hit clustered indexes on the core entities or the easy-to-maintain intermediate index on the join entity. 相反,您可以获取公会的领导者列表,无论查询方向如何,您都可以查看核心实体上的聚簇索引或联接实体上易于维护的中间索引。

Like I noted at the start of this way to long "answer", when I run into issues like yours, it's typically because I am going against the grain of Entity. 就像我在这种方式开始时注意到长期“回答”一样,当我遇到像你这样的问题时,通常是因为我违背了实体的细节。 I encourage you to re-think your data model and how with a lower friction approach - loose the multiple schema and add an intermediate guild_leader object. 我鼓励您重新思考您的数据模型以及如何使用低摩擦方法 - 松开多个模式并添加中间guild_leader对象。 Cheers! 干杯!

Unless you explicitly say, that Member entity should be mapped to acc.Members , EF will expect it to be in dbo schema Members table. 除非您明确说明,该Member实体应该映射到acc.Members ,EF将期望它在dbo架构Members表中。 For this you need to provide either EntityTypeConfiguration for this type or annotate it with System.ComponentModel.DataAnnotations.Schema.TableAttribute like [Table("acc.Members")] 为此,您需要为此类型提供EntityTypeConfiguration或使用System.ComponentModel.DataAnnotations.Schema.TableAttribute注释它,如[Table("acc.Members")]

I am answering your updated question : 我正在回答您更新的问题:

try using this line before updating your context 在更新上下文之前尝试使用此行

context.Entry(Guild.Members).State = Entity.EntityState.Unchanged

this will solve the error you have 这将解决您的错误

I know this is not relevant anymore, but I had the same problem and wanted to share my solution to anyone who comes across this post:我知道这不再相关,但我遇到了同样的问题,并想与遇到这篇文章的任何人分享我的解决方案:

I understood the question as follows:我对这个问题的理解如下:

  • You wanted to create a Database with schema "a" and "b"您想创建一个具有架构“a”和“b”的数据库
  • You wanted to have a connection between these two schema's您想在这两个模式之间建立联系
  • You wante My Solution (using EFCore 5):你想要我的解决方案(使用 EFCore 5):

DBModel Schema "a": DBModel 架构“a”:

  • Users用户

Schema "b":架构“b”:

  • Sessions会话
  • Jwts JWTs

Following code snippets are inside the OnModelCreating method of the context for schema "a":以下代码片段位于架构“a”上下文的 OnModelCreating 方法中:

base.OnModelCreating(builder);

builder.HasDefaultSchema("a");
        
builder.Entity<User>(entity =>
{
     entity.Property(x => x.FirstName);
     entity.Property(x => x.LastName);
});

It could happen, that EF Core notices the navigation properties on the User class and includes them in the migration, since we don't want to include them we have to specifically ignore them: EF Core 可能会注意到 User 类上的导航属性并将它们包含在迁移中,因为我们不想包含它们,因此必须专门忽略它们:

builder.Ignore<Session>();
builder.Ignore<Jwt>();

Following code snippets are inside the OnModelCreating method of the context for schema "b":以下代码片段位于架构“b”上下文的 OnModelCreating 方法中:

base.OnModelCreating(builder);

builder.HasDefaultSchema("b");
        
builder.Entity<Session>(entity =>
{
     entity.HasOne(x => x.User)
         .WithMany(x => x.Sessions)
         .HasForeignKey(x => x.UserId);

     entity.HasMany(x => x.Jwts)
         .WithOne(x => x.Session)
         .HasForeignKey(x => x.SessionId);

     entity.Property(x => x.UserAgent);
});

builder.Entity<User>(entity => {
    entity.ToTable("Users", "a", t => t.ExcludeFromMigrations())
});

This is a bit counterintuitive, because you tell EF Core to exclude the User table from the migration, but since you already created the table in the context for schema "a", there is no need to create it again so you have to exclude it.这有点违反直觉,因为您告诉 EF Core 从迁移中排除 User 表,但由于您已经在架构“a”的上下文中创建了该表,因此无需再次创建它,因此您必须排除它. Why should we not use "builder.Ignore()" then?为什么我们不应该使用“builder.Ignore()”呢? Because we have to tell EF Core the schema of the table and this is only possible through this method.因为我们必须告诉 EF Core 表的架构,这只能通过这种方法来实现。

To do this does not have any direct advantages except that you can share code more easily.这样做没有任何直接的好处,只是您可以更轻松地共享代码。 For example, you could have a base structure for a common service like User.例如,您可以为像 User 这样的公共服务建立一个基本结构。 Now you want to build upon it, but always want the User entity in you structures, without syncing the databases.现在您想在它的基础上进行构建,但始终希望您的结构中有 User 实体,而不需要同步数据库。 You can build now a UserService with a User -> Session -> Jwt structure and create any other service connected with the user structure, for example Blog Posts with a User -> Blog -> Posts, User -> Posts.您现在可以使用 User -> Session -> Jwt 结构构建一个 UserService 并创建与用户结构相关的任何其他服务,例如带有用户的博客帖子 -> 博客 -> 帖子,用户 -> 帖子。

This way you always use the same User table and don't have to sync between the services.这样您就可以始终使用相同的 User 表,而不必在服务之间进行同步。

CAREFULL: This has one major drawback, because you could slow down database performance if the specific entity has a lot of updates.小心:这有一个主要缺点,因为如果特定实体有大量更新,您可能会降低数据库性能。 This is because a specific entity can only be changed by one process, for something like a user this is not a big problem, but could become one if the specific case is met.这是因为一个特定的实体只能被一个进程改变,对于像用户这样的东西来说这不是什么大问题,但如果满足特定情况,它可能会变成一个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 DbContext.Find()和DbContext.SingleOrDefault()实体框架核心之间的区别 - Different between DbContext.Find() and DbContext.SingleOrDefault() Entity Framework Core 具有两个数据库的实体框架(完全不同的架构) - entity framework with two databases (completely different schemas ) 实体框架DbContext与不同的数据库兼容 - Entity Framework DbContext compatible with different databases 首先在不同架构中映射具有相同表的实体 Entity Framework 6 代码 - Map entity with same table in different schemas Entity Framework 6 code first 如何在不同DbContext上找到的模型之间创建关系? - How to create relationships between models that are found on different DbContext? 实体框架-使用相同的DbContext和不同的连接字符串 - Entity Framework - using the same DbContext with different connection strings 实体框架不会保存不同注入的 dbContext 中的更改 - Entity Framework won't save changes in different injected dbContext 实体框架的版本之间生成的查询不同 - Generated Query is different between versions of Entity Framework LINQ和实体框架之间有什么不同? - What's Different Between LINQ and Entity Framework? 在Entity Framework中,如何从VS2017中的不同Oracle模式生成实体 - In Entity Framework, how to generate entities from different Oracle schemas in VS2017
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM