简体   繁体   English

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

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

In an effort to avoid the use of Table Per Hierarchy (TPH) I have been looking at examples of how best to implement Table-Per-Concrete Class (TPC) inheritance in my database model. 为了避免使用Table Per Hierarchy(TPH),我一直在研究如何在我的数据库模型中最好地实现Table-Per-Concrete Class(TPC)继承。 I came across the official documentation and this article . 我遇到了官方文档本文

Below are some mock-up classes with some simple 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; }
}

The DbModelBuilder configurations used per the examples in both articles. 根据两篇文章中的示例使用的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"); 
});

The application runs successfully but when I go back to the database I find three (3) tables instead of the two (2) I expected to find. 应用程序运行成功,但当我返回数据库时,我找到三(3)个表而不是我期望找到的两个(2)表。 After a bit of testing it would appear the "BaseEntity" table is created but is never used. 经过一些测试后,似乎会创建“BaseEntity”表,但从未使用过。 Everything seems to work just fine with the exception of this empty orphaned table. 除了这个空的孤立表外,一切似乎都运行得很好。

I mess around with the DbModelBuilder configurations, eventually removing the "BaseEntity" configurations which provides the expected result; 我搞乱了DbModelBuilder配置,最终删除了提供预期结果的“BaseEntity”配置; Two (2) tables, each of them having the correct properties and functioning correctly. 两(2)个表,每个表都具有正确的属性并且功能正常。

I do one last test, rip out all the DbModelBuilder configurations, only include the two (2) DbSet properties for "Person" and "Business" and test again. 我做最后一次测试,删除所有DbModelBuilder配置,只包括“Person”和“Business”的两(2)个DbSet属性并再次测试。

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

To my surprise the project builds, goes out to the database, creates only the two tables with all the class properties including the inherited ones from the "BaseEntity" class. 令我惊讶的是,项目构建,转到数据库,只创建具有所有类属性的两个表,包括来自“BaseEntity”类的继承属性。 I can do CRUD operations without issue. 我可以毫无问题地进行CRUD操作。

After running many tests I can't find any issues with the final test and I have not been able to reproduce the duplicate key error both articles warned about. 在运行了许多测试后,我发现最终测试没有任何问题,而且我无法重现这两篇文章所警告的重复键错误。

The changes to the database were committed successfully, but an error occurred while updating the object context. 已成功提交对数据库的更改,但更新对象上下文时发生错误。 The ObjectContext might be in an inconsistent state. ObjectContext可能处于不一致状态。 Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. 内部异常消息:AcceptChanges无法继续,因为对象的键值与ObjectStateManager中的另一个对象冲突。 Make sure that the key values are unique before calling AcceptChanges. 在调用AcceptChanges之前,请确保键值是唯一的。

  1. I am curious why the examples use the MapInheritedProperties property; 我很好奇为什么这些例子使用MapInheritedProperties属性; is this an outdated method? 这是一种过时的方法吗?
  2. Why do both examples say to include configuration properties for the "BaseEntity" yet including either the DbSet property or any DbModelBuilder configurations for the "BaseEntity" class causes an unused table to be created. 为什么两个示例都说包含“BaseEntity”的配置属性,但包含DbSet属性或“BaseEntity”类的任何DbModelBuilder配置会导致创建未使用的表。
  3. In reference to the unique key error the articles warned of; 关于文章警告的唯一关键错误; I am unable to reproduce the error and I have tested many times with the primary key as either an int generated by the database and a guid generated by the database. 我无法重现错误,我已经多次使用主键测试数据库生成的int和数据库生成的guid。 Is the information about this error also obsolete or is there a test I can run to produce said error? 有关此错误的信息是否也已过时,或者是否存在可以运行以产生所述错误的测试?

Just to make this all simpler, I've moved the code necessary to force TablePerConcrete to open source. 为了简化这一点,我已经移动了强制TablePerConcrete开源的必要代码。 Its purpose is to allow features normally only available in the Fluent Interface (where you have to scatter a lot of code into your Db class' OnModelCreating method) to migrate over to Attribute-based features. 其目的是允许通常仅在Fluent界面中提供的功能(您必须将大量代码分散到Db类的OnModelCreating方法中)才能迁移到基于属性的功能。

It allows you to do things like this: 它允许你做这样的事情:

[TablePerConcrete]
public class MySubclassTable : MyParentClassEntity

Forcing TPC regardless of what EF might decide to infer from your parent class/subclass relationship. 强制TPC,无论EF可能决定从您的父类/子类关系推断出什么。

One interesting challenge here is that sometimes EF will screw up an inherited Id property, setting it to be filled with an explicit value rather than being database-generated. 这里一个有趣的挑战是,有时EF会搞砸一个继承的Id属性,将其设置为填充显式值而不是数据库生成。 You can ensure it doesn't do that by having the parent class implement interface IId (which just says: This has an Id property), then marking the subclasses with [ForcePKId] . 你可以通过让父类实现接口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.

Kicking off the code that handles all this for you is pretty simple - just add a couple lines to your Db class: 开始为您处理所有这些的代码非常简单 - 只需在您的Db类中添加几行:

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

You can access it one of 2 ways: 您可以通过以下两种方式之一访问它:

  1. Via a single gist with all the relevant classes copy-pasted into a single file, here: https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9 通过一个要点将所有相关类复制粘贴到一个文件中,在这里: https//gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9

  2. Via a library, our Brass9.Data, which we're releasing open source. 通过库,我们的Brass9.Data,我们正在发布开源。 It has a lot of other EF6 tools in it, like Data Migrations. 它还有很多其他的EF6工具,比如Data Migrations。 It's also more organized, with classes broken out into separate files as you'd normally expect: https://github.com/b9chris/Brass9.Data 它也更有条理,按照您通常的预期将类拆分为单独的文件: https//github.com/b9chris/Brass9.Data

I use mapping classes, but never-mind. 我使用映射类,但从不介意。 I solve it like this: 我像这样解决它:

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

}

Remember - base class must be abstract. 记住 - 基类必须是抽象的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM