簡體   English   中英

不同 DbContext 和不同模式之間的實體框架關系

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

所以,我有兩個主要對象,Member 和 Guild。 一個會員可以擁有一個公會,一個公會可以擁有多個會員。

我在單獨的 DbContext 和單獨的類庫中有成員類。 我計划在多個項目中重用這個類庫並幫助區分,我將數據庫模式設置為“acc”。 我對這個庫進行了廣泛的測試,可以添加、刪除和更新 acc.Members 表中的成員。

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

映射為:

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 項目,並將對成員類的映射添加到 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"}

如何跨 DbContext 和不同的數據庫模式使用這兩個表之間的外鍵?

更新

我縮小了錯誤的原因。 當我創建一個新的公會時,我將公會領導的Member ID 設置為MemberID。 這工作正常。 但是,當我然后嘗試將該領導者的 Member 對象添加到 Guild's List of Members (Members) 時,這就是導致錯誤的原因。

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

最終決議

因此,我必須在包含Guild DbContext 中添加到MemberRolePermission映射。 我必須添加 Role 和 Permission 因為 Member 有List<Role> Roles並且每個 Role 都有List<Permission> Permissions

這讓我更接近解決方案。 我仍然收到如下錯誤:

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

在這里,當您從Session拉出 Member 時,您會得到如下內容:

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% 的工作。 我不得不改變我的 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();

這是工作代碼:

在裝配“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());

碼:

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查詢,所以它應該具有所有必需的映射信息。

您可以將相同類型注冊到兩個不同的上下文中,即使是來自不同的程序集也是如此。 所以你可以在Guild的程序集中的上下文中映射Member ,讓我們稱它為contextG ,但僅限於

  1. Member不引用contextG映射的其他類型。 這可能意味着必須明確忽略Member中的導航屬性。
  2. Member不能引用contextG類型,因為這些類型不是Member上下文的一部分。

如果無法滿足任何這些條件,您可以做的最好的事情是在Guild的程序集中創建一個新的Member類,並在上下文中注冊它的映射。 也許您想使用不同的名稱來防止歧義,但這是唯一的替代選擇。

我發現當我遇到Entity問題並建立關系時,通常是因為我反對框架的流程或試圖建立技術上不完善的關系或抽象。 我在這里建議的是快速退一步並分析一些事情,然后再深入研究這個具體問題。

首先,我很好奇為什么你在這里使用不同的模式來獲取訪問對象圖的單個應用程序。 在某些情況下,多個模式可能很有用,但我認為Brent Ozar在本文中提出了一個非常突出的觀點。 鑒於這些信息,我傾向於首先建議您將多個模式合並為一個並推送到數據庫使用單個數據庫上下文。

接下來要解決的是對象圖。 至少對我而言,我在數據建模方面遇到的最大困難是,當我不首先弄清楚應用程序對數據庫有什么問題時。 我的意思是首先弄清楚應用程序在各種上下文中想要的數據,然后看看如何在關系上下文中優化這些數據結構的性能。 讓我們看看如何做到這一點......

根據您上面的模型,我可以看到域中有一些關鍵術語/對象:

  • 公會的集合
  • 會員集
  • 公會領袖的集合。

此外,我們還有一些需要實施的業務規則:

  • 公會可以有1個領導者(可能超過1個?)
  • 公會領袖必須是會員
  • 公會有一個0或更多成員的列表
  • 會員可以屬於公會(可能超過1個?)

因此,鑒於此信息,我們將調查您的應用程序可能對此數據模型提出的問題。 我的申請可以:

  • 查找成員並查看其屬性
  • 查找成員並查看他們的屬性,他們是公會領導者
  • 查找一個公會並查看其所有成員的列表
  • 查找一個公會並查看公會領袖名單
  • 查看所有公會領袖的名單

好吧,現在我們可以說到正如他們所說的那樣......

在這種情況下,在公會和成員之間使用聯接表是最佳的。 它將為您提供在多個公會中擁有成員或沒有公會的能力,並提供低鎖定更新策略 - 這么好的通話!

轉移到公會領袖,有一些選擇可能有意義。 雖然可能永遠不會有公會軍士的情況,但我認為考慮一個名為公會領袖的新實體是有道理的。 這種方法允許的是幾個方面。 您可以在應用程序中緩存公會領導者ID列表,因此,您可以點擊只有領導者ID列表而不是整個領導者對象的本地應用程序緩存,而不是通過db行程來授權領導者采取的公會行動。 相反,您可以獲取公會的領導者列表,無論查詢方向如何,您都可以查看核心實體上的聚簇索引或聯接實體上易於維護的中間索引。

就像我在這種方式開始時注意到長期“回答”一樣,當我遇到像你這樣的問題時,通常是因為我違背了實體的細節。 我鼓勵您重新思考您的數據模型以及如何使用低摩擦方法 - 松開多個模式並添加中間guild_leader對象。 干杯!

除非您明確說明,該Member實體應該映射到acc.Members ,EF將期望它在dbo架構Members表中。 為此,您需要為此類型提供EntityTypeConfiguration或使用System.ComponentModel.DataAnnotations.Schema.TableAttribute注釋它,如[Table("acc.Members")]

我正在回答您更新的問題:

在更新上下文之前嘗試使用此行

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

這將解決您的錯誤

我知道這不再相關,但我遇到了同樣的問題,並想與遇到這篇文章的任何人分享我的解決方案:

我對這個問題的理解如下:

  • 您想創建一個具有架構“a”和“b”的數據庫
  • 您想在這兩個模式之間建立聯系
  • 你想要我的解決方案(使用 EFCore 5):

DBModel 架構“a”:

  • 用戶

架構“b”:

  • 會話
  • JWTs

以下代碼片段位於架構“a”上下文的 OnModelCreating 方法中:

base.OnModelCreating(builder);

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

EF Core 可能會注意到 User 類上的導航屬性並將它們包含在遷移中,因為我們不想包含它們,因此必須專門忽略它們:

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

以下代碼片段位於架構“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())
});

這有點違反直覺,因為您告訴 EF Core 從遷移中排除 User 表,但由於您已經在架構“a”的上下文中創建了該表,因此無需再次創建它,因此您必須排除它. 為什么我們不應該使用“builder.Ignore()”呢? 因為我們必須告訴 EF Core 表的架構,這只能通過這種方法來實現。

這樣做沒有任何直接的好處,只是您可以更輕松地共享代碼。 例如,您可以為像 User 這樣的公共服務建立一個基本結構。 現在您想在它的基礎上進行構建,但始終希望您的結構中有 User 實體,而不需要同步數據庫。 您現在可以使用 User -> Session -> Jwt 結構構建一個 UserService 並創建與用戶結構相關的任何其他服務,例如帶有用戶的博客帖子 -> 博客 -> 帖子,用戶 -> 帖子。

這樣您就可以始終使用相同的 User 表,而不必在服務之間進行同步。

小心:這有一個主要缺點,因為如果特定實體有大量更新,您可能會降低數據庫性能。 這是因為一個特定的實體只能被一個進程改變,對於像用戶這樣的東西來說這不是什么大問題,但如果滿足特定情況,它可能會變成一個。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM