为了避免使用按表分层结构(TPH),我一直在研究如何在数据库模型中最佳实现按表具体类(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配置,仅包括“人”和“业务”的两(2)个DbSet属性,然后再次测试。
public DbSet<Person> People { get; set; }
public DbSet<Business> Businesses { get; set; }

令我惊讶的是,该项目将构建,然后进入数据库,仅创建具有所有类属性的两个表,包括从“BaseEntity”类继承的表。我可以毫无问题地进行CRUD操作。

在运行了许多测试之后,我找不到最终测试的任何问题,而且我也无法重现两篇文章都警告过的重复键错误。


  • 我很好奇为什么示例使用MapInheritedProperties属性;这是过时的方法吗?
  • 为什么两个示例都说包含“BaseEntity”的配置属性,但同时包含“BaseEntity”类的DbSet属性或任何DbModelBuilder配置,则会导致创建未使用的表。
  • 关于文章警告的唯一键错误;我无法重现该错误,并且使用主键作为数据库生成的int和数据库生成的guid进行了多次测试。有关此错误的信息是否也已过时,或者是否可以运行测试以产生所述错误?
  • 最佳答案

    为了使这一切变得更简单,我已经移动了强制TablePerConcrete开源的必要代码。其目的是允许通常仅在Fluent接口(interface)中可用的功能(您必须在其中将大量代码分散到Db类的OnModelCreating方法中)才能迁移到基于属性的功能。

    它允许您执行以下操作:

    [TablePerConcrete]
    public class MySubclassTable : MyParentClassEntity
    

    不管EF可能决定从您的父类/子类关系中推断出什么,都强制使用TPC。

    这里一个有趣的挑战是,有时EF会破坏继承的Id属性,将其设置为使用显式值填充,而不是由数据库生成。您可以通过使父类实现IId接口(interface)(它只是说:这具有一个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);
    

    您可以通过以下两种方式之一进行访问:
  • 通过单个要点将所有相关类复制粘贴到单个文件中,此处:https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9
  • 通过我们的Brass9.Data库,我们将发布开源库。它具有许多其他EF6工具,例如数据迁移。它也更加井井有条,按您通常的期望将类分为多个单独的文件:https://github.com/b9chris/Brass9.Data
  • 10-06 01:09