Today, I struggled a bit with EF5 and getting a job-type inheritance hierarchy up and running.
I’m using EF5, code first (with an existing DB reverse-engineered using the EF Power Tools, but this does not matter in this case).
The goal is to have a single table named “Job” in the DB, and a domain model with an abstract Job class with several concrete subclasses eg. “Regional Job” and “Transit Job”.
I want this hierarchy to map to a single Job table in the DB (for performance/simplicity. I’m aware that this breaks the third normal form).
In the existing DB, the Job table has a foreign key relationship with a JobType table. I would like this JobType table to also be a part of the domain model (e.g. to be able to show a list of the current job types).
If you try (like I did) to just add the following to your DbContext-derived class (as described here):
modelBuilder.Entity() .Map(m => m.Requires("TypeId").HasValue(1)) .Map(m => m.Requires("TypeId").HasValue(2));
You will get the following error:
error 3032: Problem in mapping fragments starting at line xyz:Condition member 'Job.TypeId' with a condition other than 'IsNull=False' is mapped. Either remove the condition on Job.TypeId or remove it from the mapping.
To fix this, ensure that your model classes do not include mapping of properties involved in the inheritance mapping just defined in the DbContext-derived class. In the above case, this means:
- Remove the TypeId property from the Job class. (Job.cs file).
- Remove the navigation property of the Job class, referencing the JobType class (Job.cs file, remove the virtual JobType property).
- Remove the relationship mapping in the Job class to the JobType class (JobMap.cs file, find and remove the this.HasRequired(t => t.JobType …) statement).
- Remove the navigation property of the JobType class, referencing the Job class (JobType.cs, remove the virtual Jobs property).
With this in place, you can model the desired inheritance hierarchy, map it to a single DB table and keep the JobType table in both the data and domain model.
Note that you do not need to remove the JobType table from the model, as others have indicated (e.g. here). The key to this is to make sure that the foreign key relationship is completely removed. In the above case, the Job table references the JobType table via the TypeId column, and hence the navigation property from Job to JobType and from JobType back to the Job table must both be removed.
The TypeId column is now referred to as the discriminator column – the column that EF can use to determine the type of the object at runtime.
If you want to map an inheritance hierarchy in your domain model to a single table in the DB like in the above case, a discriminator column is required. This allows EF to determine which object to materialize when you issue LINQ statements involving the inheritance hierarchy.
The key to realize is that the discriminator column cannot be used for anything else, and EF ensures this by only allowing you to map the discriminator column once. (See also one of Ladislav’s many good answers on SO).
I think this rule makes sense, since the type of an object should be immutable, once instantiated. Being able to see and possibly change the discriminator property could allow you to change the type of an object.
Leave a Reply