entity framework6中的每个具体类型的表(TPC)inheritance(EF6)

为了避免使用Table Per Hierarchy(TPH),我一直在研究如何在我的数据库模型中最好地实现Table-Per-Concrete Class(TPC)inheritance。 我遇到了官方文档和本文 。

下面是一些带有一些简单inheritance的模拟类。

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() .Property(c => c.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); modelBuilder.Entity().Map(m => { m.MapInheritedProperties(); m.ToTable("Person"); }); modelBuilder.Entity().Map(m => { m.MapInheritedProperties(); m.ToTable("Business"); }); 

应用程序运行成功,但当我回到数据库时,我找到三(3)个表而不是我期望找到的两个(2)表。 经过一些测试后,似乎会创建“BaseEntity”表,但从未使用过。 除了这个空的孤立表外,一切似乎都运行得很好。

我搞乱了DbModelBuilder配置,最终删除了提供预期结果的“BaseEntity”配置; 两(2)个表,每个表都具有正确的属性并且function正常。

我做最后一次测试,删除所有DbModelBuilder配置,只包括“Person”和“Business”的两(2)个DbSet属性并再次测试。

 public DbSet People { get; set; } public DbSet Businesses { get; set; } 

令我惊讶的是,项目构建,转到数据库,只创建具有所有类属性的两个表,包括来自“BaseEntity”类的inheritance属性。 我可以毫无问题地进行CRUD操作。

在运行了许多测试后,我发现最终测试没有任何问题,而且我无法重现两篇文章所警告的重复键错误。

已成功提交对数据库的更改,但更新对象上下文时发生错误。 ObjectContext可能处于不一致状态。 内部exception消息:AcceptChanges无法继续,因为对象的键值与ObjectStateManager中的另一个对象冲突。 在调用AcceptChanges之前,请确保键值是唯一的。

  1. 我很好奇为什么这些例子使用MapInheritedProperties属性; 这是一种过时的方法吗?
  2. 为什么两个示例都说包含“BaseEntity”的配置属性,但是包含DbSet属性或“BaseEntity”类的任何DbModelBuilder配置会导致创建未使用的表。
  3. 关于文章警告的唯一关键错误; 我无法重现错误,我已经使用主键多次测试数据库生成的int和数据库生成的guid。 有关此错误的信息是否已过时,或者是否存在可以运行以产生所述错误的测试?

为了简化这一点,我已经移动了强制TablePerConcrete开源的必要代码。 其目的是允许通常仅在Fluent界面中提供的function(您必须将大量代码分散到Db类’OnModelCreating方法中)以迁移到基于属性的function。

它允许你做这样的事情:

 [TablePerConcrete] public class MySubclassTable : MyParentClassEntity 

强制TPC,无论EF可能决定从您的父类/子类关系推断出什么。

这里有一个有趣的挑战是,有时EF会搞砸一个inheritance的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 { public PersonMap() { Map(m => { m.ToTable("Person"); m.MapInheritedProperties(); }); HasKey(p => p.Id); Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } } 

记住 – 基类必须是抽象的。