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

时间:2015-06-02 23:27:05

标签: c# entity-framework foreign-keys repository dbcontext

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

我在一个单独的DbContext和单独的类库中有Members类。我打算在多个项目中重用这个类库并帮助区分,我将数据库模式设置为“acc”。我已经广泛测试了这个库,可以在acc.Members表中添加,删除和更新成员。

行会类是这样的:

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。

我引用了Member的EF项目,并将映射到Members类添加到Guild类所属的DbContext中。 modelBuilder.Configurations.Add(new MemberMapping()); (不确定这是否是最佳方式。)

导致此错误:

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

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

更新

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

更新2

以下是我如何创建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; }
}

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

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

最终解决方案

因此,我必须在包含Member的DbContext中向RolePermissionGuild添加映射。我必须添加角色和权限,因为成员有List<Role> Roles,每个角色都有List<Permission> Permissions

这让我更接近解决方案。我仍然遇到如下错误:

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

在这里,当您从Session中提取会员时,您会收到以下内容:

System.Data.Entity.DynamicProxies.Member_FF4FDE3888B129E1538B25850A445893D7C49F878D3CD40103BA1A4813EB514C

实体框架似乎并不适合这一点。为什么?我不确定,但我认为这是因为ContextM创建了Member的代理,并且通过将Member克隆到新的Member对象中,ContextM不再具有关联。我认为,这允许ContextG自由使用新的Member对象。我尝试在我的DbContexts中设置ProxyCreationEnabled = false,但是从Session中取出的Member对象保持为System.Data.Entity.DynamicProxies.Member类型。

所以,我做的是:

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

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

这让我99%的路程。我不得不改变我的回购以及我如何保存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();

4 个答案:

答案 0 :(得分:11)

这是有效的代码:

在装配“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映射,尽管WillCascadeOnDelete(false)映射中包含LeaderMemberInfo
  • modelBuilder.Configurations.Add(new GuildMapping());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)

这在关联现有对象时也有效。

更一般情况的原始答案:

您无法将不同上下文中的类型组合到一个对象图中。这意味着,你不能做像

这样的事情
from a in context.As
join b in context.Bs on ...

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

您可以将相同的类型注册到两个不同的上下文中,即使是来自不同的程序集也是如此。因此,您可以在Member程序集中的上下文中映射Guild,让我们将其称为contextG,但仅限于

  1. Member未引用contextG映射的其他类型。这可能意味着必须明确忽略Member中的导航属性。
  2. Member无法引用contextG中的类型,因为这些类型不属于Member的上下文。
  3. 如果无法满足任何这些条件,您可以做的最好的事情是在Member程序集中创建一个新的Guild类,并在上下文中注册其映射。也许您想使用不同的名称来防止歧义,但这是唯一的替代方案。

答案 1 :(得分:1)

我发现当我遇到实体问题并建立关系时,通常是因为我反对框架的流程或试图建立技术上不完善的关系或抽象。我在这里建议的是在深入研究这个具体问题之前,先快速退一步并分析一些事情。

首先,我很好奇为什么你在这里使用不同的模式来处理访问对象图的单个应用程序。在某些情况下,多个模式可能很有用,但我认为Brent Ozar在这个article中提出了一个非常突出的观点。鉴于这些信息,我倾向于首先建议您将多个模式合并为一个并推送到数据库使用单个数据库上下文。

接下来要解决的是对象图。至少对我而言,我在数据建模方面遇到的最大困难是,当我不首先弄清楚应用程序对数据库有什么问题时。我的意思是首先弄清楚应用程序在各种上下文中想要的数据,然后看看如何在关系上下文中优化这些数据结构的性能。让我们看看如何做到这一点......

根据您上面的模型,我可以看到域中有几个关键术语/对象:

  • 行会集合
  • 会员收藏
  • 公会领袖的集合。

此外,我们还有一些需要实施的业务规则:

  • 公会可以有1个领导者(可能超过1个人)
  • 行会领袖必须是会员
  • A Guild有0个或更多成员的列表
  • 会员可以属于公会(可能超过1?)

因此,鉴于此信息,我们将调查您的应用程序可能对此数据模型有哪些问题。我的申请可以:

  • 查找会员并查看其属性
  • 查找会员并查看他们的属性,他们是公会领导
  • 查找公会并查看其所有成员的列表
  • 查找一个公会并查看公会领袖名单
  • 查找所有公会领袖的名单

好的,现在我们可以说到正如他们所说的那样......

在这种情况下,在公会和成员之间使用联接表是最佳选择。它将为您提供在多个公会或没有公会的情况下拥有成员并提供低锁定更新策略的能力 - 如此好的通话!

转向公会领袖,有一些选择可能有意义。虽然可能永远不会有公会军士的情况,但我认为考虑一个名为公会领袖的新实体是有道理的。这种方法允许的是几个方面。您可以在应用程序中缓存公会领导者ID列表,因此,您可以点击只有领导者ID列表而不是整个领导者对象的本地应用程序缓存,而不是通过db行程来授权领导者采取的公会行动。相反,您可以获取公会的领导者列表,无论查询方向如何,您都可以查看核心实体上的聚簇索引或联接实体上易于维护的中间索引。

就像我在这种方式开始时注意到长期“回答”一样,当我遇到像你这样的问题时,通常是因为我违背了实体的细节。我鼓励您重新思考您的数据模型以及如何使用低摩擦方法 - 松开多个模式并添加中间guild_leader对象。干杯!

答案 2 :(得分:1)

除非您明确说明,Member实体应映射到acc.Members,否则EF会希望它位于dbo架构Members表中。为此,您需要为此类型提供EntityTypeConfiguration或使用System.ComponentModel.DataAnnotations.Schema.TableAttribute注释[Table("acc.Members")]

答案 3 :(得分:0)

我正在回答您更新的问题:

在更新上下文之前尝试使用此行

{{1}}

这将解决您的错误

相关问题