不同 DbContext 和不同模式之间的实体框架关系 [英] Entity Framework relationships between different DbContext and different schemas

查看:31
本文介绍了不同 DbContext 和不同模式之间的实体框架关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,我有两个主要对象,会员和公会.一个会员可以拥有一个公会,一个公会可以有多个会员.

So, I have two main objects, Member and Guild. One Member can own a Guild and one Guild can have multiple Members.

我在单独的 DbContext 和单独的类库中有 Members 类.我计划在多个项目中重用这个类库并帮助区分,我将数据库模式设置为acc".我已经对这个库进行了广泛的测试,并且可以在 acc.Members 表中添加、删除和更新成员.

I have the Members class in a separate DbContext and separate class library. I plan to reuse this class library in multiple projects and to help differentiate, I set the database schema to be "acc". I have tested this library extensively and can add, delete, and update Members in the acc.Members table.

Guild 类是这样的:

The Guild class is as such:

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; }
}

具有以下映射:

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");
            });
    }
}

但是,当我尝试创建一个新的公会时,它说没有 dbo.Members.

But, when I try to create a new Guild, it says that there is no dbo.Members.

我获得了对成员的 EF 项目的引用,并将成员类的映射添加到公会类所属的 DbContext 中.modelBuilder.Configurations.Add(new MemberMapping());(不确定这是否是最好的方法.)

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. modelBuilder.Configurations.Add(new MemberMapping()); (Not sure if that is the best way.)

这导致了这个错误:

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

如何利用这两个表之间的外键跨 DbContexts 和不同的数据库模式?

How can I utilize the foreign key between these two tables cross DbContexts and with different database schemas?

更新

我缩小了错误的原因.当我创建一个新的公会时,我将公会领导的成员 ID 设置为 MemberID.这工作正常.但是,当我尝试将该领导的成员对象添加到公会的成员列表(成员)中时,这就是导致错误的原因.

I narrowed down the cause of the error. When I create a new guild, I set the guild leader's Member ID to 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.

更新 2

这是我如何创建 Guild 类所在的上下文的代码.(应 Hussein Khalil 的要求)

Here is the code of how I create the Context that the Guild class is in. (As requested by 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; }
}

这就是我在 repo 中保存它的方式:

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();
        }
    }

最终解决方案

所以,我必须在包含 Guild 的 DbContext 中添加对 MemberRolePermission 的映射.我必须添加角色和权限,因为成员有 List<Role>角色,每个角色都有List<Permission>权限.

So, I had to add mappings to Member, Role, and Permission in DbContext that contained Guild. I had to add Role and Permission because Member had List<Role> Roles and each Role had 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.
Parameter name: identity"}

这里,当你从 Session 中拉取 Member 时,你会得到这样的结果:

Here, when you pull Member from the Session, you get something like this:

System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C

Entity Framework 似乎不能很好地处理这个问题.为什么?我不确定,但我认为这是因为 ContextM 创建了 Member 的代理,并且通过将 Member 克隆到新的 Member 对象中,ContextM 不再具有关联.我认为,这允许 ContextG 自由使用新的 Member 对象.我尝试在我的 DbContexts 中设置 ProxyCreationEnabled = false,但是从 Session 中拉出的 Member 对象的类型仍然是 System.Data.Entity.DynamicProxies.Member.

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. This, I think, allows ContextG to use the new Member object freely. 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.

所以,我所做的是:

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

我必须在各自的构造函数中克隆每个 Role 和每个 Permission.

I had to clone each Role and each Permission as well inside their respective constructors.

这让我走了 99% 的路.我不得不更改我的存储库以及如何保存 Guild 对象.

This got me 99% of the way there. I had to alter my repo and how I was saving the Guild object.

            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();

推荐答案

这是工作代码:

在汇编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();
    }
}

在程序集G"中:

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

代码:

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();

执行的 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 ...

...因为总是有一个上下文应该创建整个 SQL 查询,所以它应该具有所有必需的映射信息.

...because there's always one context that should create the whole SQL query, so it should have all required mapping information.

您可以将相同的类型注册到两个不同的上下文中,即使是来自不同的程序集.所以你可以在 Guild 的程序集的上下文中映射 Member,我们称之为 contextG,但前提是

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

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

如果无法满足这些条件中的任何一个,您可以做的最好的事情是在 Guild 的程序集中创建一个新的 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. Maybe you want to use a different name to prevent ambiguity, but this is about the only alternative left.

这篇关于不同 DbContext 和不同模式之间的实体框架关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆