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

2024-03-24

为了避免使用每个层次结构表 (TPH),我一直在研究如何在我的数据库模型中最好地实现每个具体类表 (TPC) 继承的示例。我遇到了官方文档 https://msdn.microsoft.com/en-us/data/jj591617#2.6 and 本文 https://datatellblog.wordpress.com/2015/04/12/inheritance-modelling-patterns-with-entity-framework-6-code-first/.

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

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”的配置属性,但包含“BaseEntity”类的 DbSet 属性或任何 DbModelBuilder 配置会导致创建未使用的表。
  3. 关于文章警告的唯一关键错误;我无法重现该错误,并且我已经使用主键作为数据库生成的 int 和数据库生成的 guid 进行了多次测试。有关此错误的信息是否也已过时,或者是否可以运行测试来产生所述错误?

为了让这一切变得更简单,我已经移动了强制 TablePerConcrete 开源所需的代码。其目的是允许通常仅在 Fluent Interface 中可用的功能(您必须将大量代码分散到 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 https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9

  2. 通过我们的 Brass9.Data 库,我们将其开源。它包含许多其他 EF6 工具,例如数据迁移。它也更有组织性,如您通常期望的那样,类被分解为单独的文件:https://github.com/b9chris/Brass9.Data https://github.com/b9chris/Brass9.Data

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐