为了避免使用每层次结构表 (TPH),我一直在研究如何在我的数据库模型中最好地实现每具体类表 (TPC) 继承的示例。我看到了官方文档和这篇文章。
下面是一些具有一些简单继承的模型类。
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 之前是唯一的。
为了让这一切更简单,我已经移动了强制 TablePerConcrete 开源所需的代码。它的目的是允许通常仅在 Fluent Interface 中可用的功能(您必须将大量代码分散到 Db 类的 OnModelCreating 方法中)迁移到基于属性的功能。
它允许你做这样的事情:
[TablePerConcrete]
public class MySubclassTable : MyParentClassEntity
无论 EF 可能决定从您的父类/子类关系中推断出什么,都强制执行 TPC。
这里一个有趣的挑战是,有时 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);
您可以通过以下两种方式之一访问它:
通过一个要点将所有相关类复制粘贴到一个文件中,这里:https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9
通过一个库,我们的 Brass9.Data,我们正在发布开源。它有很多其他的 EF6 工具,比如数据迁移。它也更有条理,如您通常期望的那样将类分解为单独的文件:https://github.com/b9chris/Brass9.Data
我使用映射类,但没关系。我是这样解决的:
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);
}
}
记住——基类必须是抽象的。
set BaseEntity 是抽象类,不在数据库中创建表。