I have a legacy database that has a few tables which were designed using Polymorphic Associations. By polymorphic associations, I mean that those tables can be child of different tables according to a column ObjectType
.
Example:
Documents
table hasDocumentID
(identity primary key), some other columns, and 2 special columns calledObjectType
andObjectID
.- If
ObjectType='STUDENT'
,ObjectID
points toStudents
table. - If
ObjectType='TEACHER'
,ObjectID
points toTeachers
table, etc.
This is similar to this design (as described in the "no foreign key approach") or this one (described as an anti-pattern). And obviously, there are no foreign key constraints on those columns (AFAIK no database would allow that kind of relationship).
I'm writing a new Data Access Layer using Entity Framework 6 (code-first with Fluent API) which should work side-by-side with existing code. Since this database structure was deployed to hundreds of different customers (each one with a different codebase and different database customizations), modifying the structure of existing tables is not an option.
My question is: How do I map those Polymorphic Associations into my EF code-first model?
EDIT: It seems that I was trying to design the class hierarchy on the wrong entities. My understanding was that I had a lot of "GenericChildTables" (like Documents) that should point to a (non-existing) entity that would have a composite key ObjectType+ObjectID. And then I was trying to create that new entity (let's call it "BusinessObject") and map my core entities (Students, Teachers, etc) to be subtypes of this BusinessObject.
THAT design was probably just plain wrong, and maybe totally impossible because this new table that I was creating (BusinessObject) depended on StudentID/TeacherID, etc, so it couldn't be a parent of those tables. Using some ugly workarounds I could create that BusinessObject as a single-child for each core entity, and map those BusinessObjects to the polymorphic tables, and it was working indeed but in a completely wrong design.
Then I saw Gert Ardold's question and realized that what should be designed as a class hierarchy was NOT Students/Teachers/etc (grouped into a generic entity), but each one of those ChildTables, which were holding different subtypes according to the ObjectType
discriminator - those were the types that should be splitted into subtypes. See my solution on my own answer below.
It seems that I was trying to design the class hierarchy on the wrong entities. My understanding was that I had a lot of "GenericChildTables" (like Documents) that should point to a (non-existing) entity that would have a composite key ObjectType+ObjectID. And then I was trying to create that new entity (let's call it "BusinessObject") and map my core entities (Students, Teachers, etc) to be subtypes of this BusinessObject.
Then I saw Gert Ardold's question and realized that the correct inheritance design was NOT about grouping Students/Teachers/etc into a supertype, but about splitting those GenericChildTables into multiple subtypes.
I'll use the Documents table as an example to show how I converted those GenericChildTables into a TPH, and how I mapped my core entities (Students, Teachers, etc) to collections of those subtypes.
First, I created the derived classes (subtypes), added navigation properties, and mapped those subtypes to the base type using
ObjectType
as type discriminator:Then I added the navigation properties to my core classes (Student and Teachers), pointing to the subtypes created:
I created the mappings for relationships Student.Documents and Teacher.Documents. Please note that I use properties StudentID and TeacherID, but they are phisically mapped to ObjectID column:
Finally, I removed from the base type (Document) the property
ObjectType
, because it is a type discriminator, and should be used only internally (cannot be exposed on class).I also removed from the base type
ObjectID
because this should be mapped only on the subtypes (mapped respectively as StudentID and TeacherID).And everything worked like a charm!
PS: Please note that if you are using T4 templates (code first from database) they will always regenerate those properties, because templates know nothing about hiearchies, so they map Documents into a single entity with properties for every column, so you should manually exclude those properties.