Entity Framework 中的多态关联
Polymorphic Associations in Entity Framework
我有一个遗留数据库,其中有几个 table 是使用 Polymorphic Associations 设计的。通过多态关联,我的意思是根据列 ObjectType
,那些 table 可以是不同 table 的子级。
示例:
Documents
table 具有 DocumentID
(标识主键)、一些其他列和 2 个名为 ObjectType
和 ObjectID
的特殊列。
- 如果
ObjectType='STUDENT'
,ObjectID
指向Students
table。
- 如果
ObjectType='TEACHER'
,ObjectID
指向Teachers
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 模板(首先从数据库编码),它们将始终重新生成这些属性,因为模板对层次结构一无所知,因此它们将文档映射到具有属性的单个实体中对于每一列,因此您应该手动排除这些属性。
我有一个遗留数据库,其中有几个 table 是使用 Polymorphic Associations 设计的。通过多态关联,我的意思是根据列 ObjectType
,那些 table 可以是不同 table 的子级。
示例:
Documents
table 具有DocumentID
(标识主键)、一些其他列和 2 个名为ObjectType
和ObjectID
的特殊列。- 如果
ObjectType='STUDENT'
,ObjectID
指向Students
table。 - 如果
ObjectType='TEACHER'
,ObjectID
指向Teachers
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 模板(首先从数据库编码),它们将始终重新生成这些属性,因为模板对层次结构一无所知,因此它们将文档映射到具有属性的单个实体中对于每一列,因此您应该手动排除这些属性。