Polymorphic Associations in Entity Framework

entity-framework entity-framework-6 polymorphic-associations sql-server

Question

A few tables in a historical database I have were created utilising the Polymorphic Relationships design pattern. Polymorphic connections refer to the fact that some tables may be the offspring of several other tables, depending on a column.ObjectType .

Example:

  • Documents has a tableDocumentID (identity main key), a few more columns, and two unique columns referred as asObjectType and ObjectID .
  • If ObjectType='STUDENT' , ObjectID refers toStudents table.
  • If ObjectType='TEACHER' , ObjectID refers toTeachers desk, etc.

This is comparable to this style or like as (as mentioned in the "no foreign key method") (described as an anti-pattern). There are clearly no restrictions on foreign keys on those columns as well.

Entity Framework 6 is being used to create a new Data Access Layer that should coexist with current code (code-first with Fluent API). Modifying the structure of existing tables is not an option since this database structure was distributed to hundreds of different clients, each with a unique codebase and database modifications.

How can I include those Polymorphic Associations into my EF code-first model, is the issue I have.


EDIT: I seem to have been attempting to create the class hierarchy based on the incorrect entities. I believed that I had a number of "GenericChildTables" (such as Documents) that should refer to a (invalid) object with a composite key of ObjectType+ObjectID. Then I was attempting to map my primary entities (students, teachers, etc.) to be subtypes of this new object, which we'll name a "BusinessObject"

Because this new table I was developing (BusinessObject) relied on StudentID/TeacherID, etc., and couldn't be a parent of those tables, THAT design was probably just plain incorrect and maybe utterly unworkable. I was able to generate the BusinessObject as a single-child for each core object and map those BusinessObjects to the polymorphic tables using some unsightly workarounds, and it was working—just not in the way I had intended.

Then When I observed The query from Gert Ardold, I understood that each of those ChildTables, which were each holding various subtypes in accordance with the class hierarchy, rather than Students/Teachers/etc. (grouped into a generic entity), should be built as a class hierarchy.ObjectType The kinds that should be divided into subtypes were discriminator. Below is my response to my own response.

1
0
5/23/2017 12:32:20 PM

Accepted Answer

I seem to have been attempting to create the class hierarchy based on the incorrect entities. I believed that I had a number of "GenericChildTables" (such as Documents) that should refer to a (invalid) object with a composite key of ObjectType+ObjectID. Then I was attempting to map my primary entities (students, teachers, etc.) to be subtypes of this new object, which we'll name a "BusinessObject"

Then I saw The query from Gert Ardold and understood that the right inheritance architecture was dividing those GenericChildTables into several subtypes rather than putting Students, Teachers, etc. into a supertype.

I'll use the Documents table as an example to demonstrate how I transformed those GenericChildTables into a TPH and how I assigned collections of those subtypes to my core entities (students, teachers, etc.).

Prior to adding navigation attributes and mapping those subtypes to the basic type, I first constructed the derived classes (subtypes).ObjectType as a type differentiator:

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");
});

The navigation attributes were then added to my main classes (Students and Teachers), linking to the newly generated subtypes:

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

For the relationships Student.Documents and Teacher.Documents, I made the mappings. Please take notice that although I utilise the attributes StudentID and TeacherID, their physical mapping is to the ObjectID column.

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);

I taken out of the basic type (Document) the property as a last step.ObjectType being a type discriminator, it should only be used internally (cannot be exposed on class).
I am also of the basic kind, zzz-56 zzz.ObjectID because only the subtypes should be mapped to this (mapped respectively as StudentID and TeacherID).

And everything worked flawlessly!

PS: Please be aware that T4 templates (code first from database) will always regenerate those properties because they have no concept of hierarchies and therefore map Documents into a single entity with properties for every column. As a result, you must manually exclude those properties if you use T4 templates.

1
5/23/2017 12:25:33 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow