实体框架6中的每个具体类型的表(TPC)继承(EF6)

时间:2015-09-15 21:51:30

标签: c# asp.net sql-server entity-framework inheritance

为了避免使用Table Per Hierarchy(TPH),我一直在研究如何在我的数据库模型中最好地实现Table-Per-Concrete Class(TPC)继承。我遇到了official documentationthis article

下面是一些带有一些简单继承的模型类。

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。有关此错误的信息是否也已过时,或者是否存在可以运行以产生所述错误的测试?

2 个答案:

答案 0 :(得分:2)

为了简化这一点,我已经移动了强制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

答案 1 :(得分:0)

我使用的是映射类,但从不介意。我这样解决:

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

}

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