【问题标题】:Polymorphic Associations in Entity Framework实体框架中的多态关联
【发布时间】:2016-07-08 21:00:45
【问题描述】:

我有一个旧数据库,其中包含一些使用Polymorphic Associations 设计的表。通过多态关联,我的意思是根据ObjectType 列,这些表可以是不同表的子表。

例子:

  • Documents 表有DocumentID(身份主键)、一些其他列和两个称为ObjectTypeObjectID 的特殊列。
  • 如果ObjectType='STUDENT'ObjectID 指向Students 表。
  • 如果ObjectType='TEACHER'ObjectID指向Teachers表等。

这类似于this design(如“无外键方法”中所述)或this one(描述为反模式)。显然,这些列没有外键约束(AFAIK 没有数据库允许这种关系)

我正在使用实体框架 6(使用 Fluent API 的代码优先)编写一个新的数据访问层,它应该与现有代码并排工作。由于这种数据库结构已部署到数百个不同的客户(每个客户都有不同的代码库和不同的数据库定制),因此修改现有表的结构不是一种选择。

我的问题是:如何将这些多态关联映射到我的 EF 代码优先模型中?


编辑:似乎我试图在错误的实体上设计类层次结构。我的理解是,我有很多“GenericChildTables”(如 Documents)应该指向一个(不存在的)实体,该实体将具有复合键 ObjectType+ObjectID。然后我尝试创建新实体(我们称之为“BusinessObject”)并将我的核心实体(学生、教师等)映射为该 BusinessObject 的子类型。

这种设计可能完全是错误的,而且可能完全不可能,因为我正在创建的这个新表 (BusinessObject) 依赖于 StudentID/TeacherID 等,因此它不可能是这些表的父级。使用一些丑陋的解决方法,我可以将 BusinessObject 创建为每个核心实体的单个子对象,并将这些 BusinessObjects 映射到多态表,它确实可以工作,但设计完全错误。

然后我看到Gert Ardold's question 并意识到应该被设计为类层次结构的不是学生/教师/等(分组为一个通用实体),而是这些子表中的每一个,其中根据ObjectType 鉴别器持有不同的子类型 - 这些类型应该被拆分为子类型。 在下面我自己的答案中查看我的解决方案。

【问题讨论】:

  • Here 是我如何使用数据库优先完成的,但也描述了我如何无法将其移植到代码优先。
  • 不,这不是正确的模型(两个外键)。
  • @GertArnold 这是朋友对我问题的回复,但我不确定它是否有效。您的设计为我指明了正确的方向,我刚刚发布了一个答案,它现在似乎正在工作。如果我正确理解了您的代码,我想问题是 OwnerId 应该从基类(注释)中删除,并且仅是派生类的一部分。

标签: sql-server entity-framework entity-framework-6 polymorphic-associations


【解决方案1】:

似乎我试图在错误的实体上设计类层次结构。我的理解是,我有很多“GenericChildTables”(如 Documents)应该指向一个(不存在的)实体,该实体将具有复合键 ObjectType+ObjectID。然后我尝试创建新实体(我们称之为“BusinessObject”)并将我的核心实体(学生、教师等)映射为该 BusinessObject 的子类型。

然后我看到Gert Ardold's question 并意识到正确的继承设计不是将 Student/Teachers/etc 分组为超类型,而是将这些 GenericChildTables 拆分为多个子类型。

我将使用 Documents 表作为示例,展示如何将这些 GenericChildTables 转换为 TPH,以及如何将我的核心实体(学生、教师等)映射到这些子类型的集合。

首先,我创建了派生类(子类型),添加了导航属性,并使用ObjectType 作为类型鉴别器将这些子类型映射到基本类型:

public class StudentDocument : Document
{
    public Student Student { get; set; }
    public int StudentID { get; set; } 
}
public class TeacherDocument : Document
{
    public Teacher Teacher { get; set; }
    public int TeacherID { get; set; } 
}
modelBuilder.Entity<Document>()
.Map<StudentDocument>(m => {
    m.Requires("ObjectType").HasValue("STUDENT");
})
.Map<TeacherDocument>(m => {
    m.Requires("ObjectType").HasValue("TEACHER");
});

然后我将导航属性添加到我的核心类(学生和教师)中,指向创建的子类型:

partial class Student
{
   public virtual ICollection<StudentDocument> Documents { get; set; }
}
partial class Teacher
{
   public virtual ICollection<TeacherDocument> Documents { get; set; }
}

我为 Student.Documents 和 Teacher.Documents 关系创建了映射。请注意,我使用属性 StudentID 和 TeacherID,但它们在物理上映射到 ObjectID 列:

var sl = modelBuilder.Entity<StudentDocument>();
sl.Property(t => t.StudentID).HasColumnName("ObjectID");
sl.HasRequired(t => t.Student).WithMany(t => t.Documents).HasForeignKey(d => d.StudentID);

var al = modelBuilder.Entity<TeacherDocument>();
al.Property(t => t.TeacherID).HasColumnName("ObjectID");
al.HasRequired(t => t.Teacher).WithMany(t => t.Documents).HasForeignKey(d => d.TeacherID);

最后,我从基类型(文档)中删除了属性ObjectType,因为它是一个类型鉴别器,并且只能在内部使用(不能在类上公开)。
我还从基本类型 ObjectID删除,因为这应该只映射到子类型(分别映射为 StudentID 和 TeacherID)。

一切都像魅力一样!

PS:请注意,如果您使用 T4 模板(代码优先来自数据库),它们将始终重新生成这些属性,因为模板对层次结构一无所知,因此它们将 Documents 映射到具有每一列属性的单个实体中,因此您应手动排除这些属性。

【讨论】:

  • 我玩过它,这似乎创建了 TPC 或 TPH,但没有 FK 分配。它产生的结果模式是什么?
  • Mardoxx,实际上我使用的是旧数据库,所以我首先使用数据库中的代码(换句话说,我没有自动生成数据库)。我真的无法评论模式生成,因为我首先对代码了解不多。
猜你喜欢
  • 1970-01-01
  • 2013-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多