簡體   English   中英

實體框架6中的每個具體類型的表(TPC)繼承(EF6)

[英]Table Per Concrete Type (TPC) Inheritance in Entity Framework 6 (EF6)

為了避免使用Table Per Hierarchy(TPH),我一直在研究如何在我的數據庫模型中最好地實現Table-Per-Concrete Class(TPC)繼承。 我遇到了官方文檔本文

下面是一些帶有一些簡單繼承的模型類。

public class BaseEntity
{
    public BaseEntity()
    {
        ModifiedDateTime = DateTime.Now;
    }

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public DateTime ModifiedDateTime { get; set; }
}

public class Person : BaseEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Business :  BaseEntity
{
    public string Name { get; set; }
    public string Location { get; set; }
}

根據兩篇文章中的示例使用的DbModelBuilder配置。

modelBuilder.Entity<BaseEntity>() 
    .Property(c => c.Id) 
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

modelBuilder.Entity<Person>().Map(m => 
{ 
    m.MapInheritedProperties(); 
    m.ToTable("Person"); 
}); 

modelBuilder.Entity<Business>().Map(m => 
{ 
    m.MapInheritedProperties(); 
    m.ToTable("Business"); 
});

應用程序運行成功,但當我返回數據庫時,我找到三(3)個表而不是我期望找到的兩個(2)表。 經過一些測試后,似乎會創建“BaseEntity”表,但從未使用過。 除了這個空的孤立表外,一切似乎都運行得很好。

我搞亂了DbModelBuilder配置,最終刪除了提供預期結果的“BaseEntity”配置; 兩(2)個表,每個表都具有正確的屬性並且功能正常。

我做最后一次測試,刪除所有DbModelBuilder配置,只包括“Person”和“Business”的兩(2)個DbSet屬性並再次測試。

public DbSet<Person> People { get; set; }
public DbSet<Business> Businesses { get; set; }

令我驚訝的是,項目構建,轉到數據庫,只創建具有所有類屬性的兩個表,包括來自“BaseEntity”類的繼承屬性。 我可以毫無問題地進行CRUD操作。

在運行了許多測試后,我發現最終測試沒有任何問題,而且我無法重現這兩篇文章所警告的重復鍵錯誤。

已成功提交對數據庫的更改,但更新對象上下文時發生錯誤。 ObjectContext可能處於不一致狀態。 內部異常消息:AcceptChanges無法繼續,因為對象的鍵值與ObjectStateManager中的另一個對象沖突。 在調用AcceptChanges之前,請確保鍵值是唯一的。

  1. 我很好奇為什么這些例子使用MapInheritedProperties屬性; 這是一種過時的方法嗎?
  2. 為什么兩個示例都說包含“BaseEntity”的配置屬性,但包含DbSet屬性或“BaseEntity”類的任何DbModelBuilder配置會導致創建未使用的表。
  3. 關於文章警告的唯一關鍵錯誤; 我無法重現錯誤,我已經多次使用主鍵測試數據庫生成的int和數據庫生成的guid。 有關此錯誤的信息是否也已過時,或者是否存在可以運行以產生所述錯誤的測試?

為了簡化這一點,我已經移動了強制TablePerConcrete開源的必要代碼。 其目的是允許通常僅在Fluent界面中提供的功能(您必須將大量代碼分散到Db類的OnModelCreating方法中)才能遷移到基於屬性的功能。

它允許你做這樣的事情:

[TablePerConcrete]
public class MySubclassTable : MyParentClassEntity

強制TPC,無論EF可能決定從您的父類/子類關系推斷出什么。

這里一個有趣的挑戰是,有時EF會搞砸一個繼承的Id屬性,將其設置為填充顯式值而不是數據庫生成。 你可以通過讓父類實現接口IId (它只是說:它有一個Id屬性)來確保不這樣做,然后使用[ForcePKId]標記子類。

public class MyParentClassEntity : IId
{
    public int Id { get; set; }
    . . .

[TablePerConcrete]
[ForcePKId]
public class MySubclassTable : MyParentClassEntity
{
    // No need for  PK/Id property here, it was inherited and will work as
    // you intended.

開始為您處理所有這些的代碼非常簡單 - 只需在您的Db類中添加幾行:

public class Db : DbContext
{
    . . .
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var modelsProject = Assembly.GetExecutingAssembly();
        B9DbExtender.New().Extend(modelBuilder, modelsProject);

您可以通過以下兩種方式之一訪問它:

  1. 通過一個要點將所有相關類復制粘貼到一個文件中,在這里: https//gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9

  2. 通過庫,我們的Brass9.Data,我們正在發布開源。 它還有很多其他的EF6工具,比如Data Migrations。 它也更有條理,按照您通常的預期將類拆分為單獨的文件: https//github.com/b9chris/Brass9.Data

我使用映射類,但從不介意。 我像這樣解決它:

public class PersonMap : EntityTypeConfiguration<Person>
{
    public PersonMap()
    {
        Map(m => { m.ToTable("Person"); m.MapInheritedProperties(); });

        HasKey(p => p.Id);
        Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
    }

}

記住 - 基類必須是抽象的。

暫無
暫無

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

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