【发布时间】:2015-09-15 21:51:29
【问题描述】:
为了避免使用逐层表 (TPH),我一直在研究如何在我的数据库模型中最好地实现逐层表 (TPC) 继承的示例。我遇到了official documentation 和this 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 之前是唯一的。
- 我很好奇这些示例为什么使用 MapInheritedProperties 属性;这是一种过时的方法吗?
- 为什么这两个示例都说包含“BaseEntity”的配置属性,但包含“BaseEntity”类的 DbSet 属性或任何 DbModelBuilder 配置会导致创建未使用的表。
- 关于文章警告的唯一键错误;我无法重现该错误,并且我已经使用主键作为数据库生成的 int 和数据库生成的 guid 进行了多次测试。有关此错误的信息是否也已过时,或者是否有可以运行的测试来产生所述错误?
【问题讨论】:
-
我认为应该创建 3 个表,尽管基础表似乎无法使用。这是 TPC 的某种缺点,您可能会在客户端代码中获得一些好处,但必须遭受一些冗余数据(甚至不应该像传统数据库设计概念那样创建)。我认为您可以尝试 TPT,而基表包含所有常见列,并且不再被视为多余。
-
@Hopeless 我没有考虑 TPT 的原因,尽管我很喜欢 OCD 的设计方面,但你从所有连接语句中获得的性能损失,甚至需要稍微回调复杂的数据。至于关于 3 个表的说法,我已经看到很多相反的文章,但我仍然没有一个可以正常工作。我想知道我设置模型的最终方式是否有一些缺点;我最终得到了两个表,没有垃圾表,没有垃圾列,没有主键问题,但我找不到我所做的示例。
-
对于 TPH,BaseEntity 类应该是抽象的。我认为仅此一项就应该只为具体类型生成两个数据库表。实际上,我确实遇到了这个 ObjectContext 处于不一致状态的错误,我能够修复它的唯一方法是在 BaseEntity 类型 Guid 而不是 Int 中创建 Id 字段(这似乎相当浪费)。
-
不一致的状态是由于 dbcontext 有一个包含所有子类的 DbSet
。它仍然需要唯一标识所有表中的实体。 TPT 这样做是因为它仍然从根本上链接回基类型的 PK,因此每个子类实例都有一个唯一的键。使用 TPC 可能会发生重叠,因此会出现错误。
标签: c# asp.net sql-server entity-framework inheritance