Entity Framework 中的多态关联

Polymorphic Associations in Entity Framework

我有一个遗留数据库,其中有几个 table 是使用 Polymorphic Associations 设计的。通过多态关联,我的意思是根据列 ObjectType,那些 table 可以是不同 table 的子级。

示例:

这类似于 this design (as described in the "no foreign key approach") or this one(描述为反模式)。显然,这些列 没有外键约束 (据我所知,没有数据库会允许这种关系)

我正在使用 Entity Framework 6(使用 Fluent API 的代码优先)编写一个新的数据访问层,它应该与现有代码并排工作。由于此数据库结构已部署到数百个不同的客户(每个客户都有不同的代码库和不同的数据库自定义),因此无法修改现有 table 的结构。

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


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

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

然后 我看到了 Gert Ardold's question 并意识到应该设计为 class 层次结构的东西不是 Students/Teachers/etc(分组到一个通用的entity),但是那些 ChildTables 中的每一个,根据 ObjectType 鉴别器持有不同的子类型——这些是应该被拆分成子类型的类型。 请参阅下面我自己的答案的解决方案。

看来我试图在错误的实体上设计 class 层次结构。我的理解是,我有很多 "GenericChildTables"(如文档)应该指向一个(不存在的)实体,该实体将具有复合键 ObjectType+ObjectID。然后我试图创建那个新实体(我们称它为 "BusinessObject")并将我的核心实体(学生、教师等)映射到此 BusinessObject 的子类型。

然后我看到了Gert Ardold's question,意识到正确的继承设计不是将Students/Teachers/etc组合成一个超类型,而是将那些GenericChildTables拆分成多个子类型。

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

首先,我创建了派生的 classes(子类型),添加了导航属性,并使用 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");
});

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

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,因为它是一个类型鉴别器,应该只使用内部(不能在 class 上公开)。
我还从基本类型 ObjectID 删除了 ,因为这应该只映射到子类型(分别映射为 StudentID 和 TeacherID)。

一切都很顺利!

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