As I alluded to on a recent post of mine, I’ve been experimenting with inheritance in EF4. EF4 (allegedly) supports 3 types: –
Table-per-Hierarchy: One physical DB table contains all fields for the entire type hierarchy. This is the only mode of inheritance in Linq to SQL.
Table-per-Type: One physical DB table stores all shared fields, whilst each type has its own table for type-specific fields.
Table-per-Concrete Type: Each type has its own physical DB table, even for common (base types) field.
Now, the first two are well documented on MSDN but the third is conspicuously absent. It turns out that there is no design-time support for this model, nor is there any real documentation on how to do it! I’ve surfed around and not really found much in the way of helpful guidance on this, so here’s my take on it.
Here’s a (very) simple set of database tables:
Let’s now push that into EF4 using the designer:
Oh look! There are two common fields. Let’s make a nice type hierarchy out of them, by creating a new abstract entity called Employee and then tying them all together (and removing the common fields from the two concrete entities):
Hit compile and…. it doesn’t work. The first problem you’ll get is that you need to map the Id and Name properties from the base Employee entity to both physical tables. How do we do that? Well, you can try to go to mapping of Employee in the designer and doing the following:
It won’t work. You still need to map the unique identifier column (ID) to both the Developer and Manager entities – but you can’t do this in the designer.
So you open up the model in the XML Editor (Open with…) and you get a whole load of XML.
SSDL: The database defintions; you won’t need to modify this.
CSDL: The conceptual model i.e. the entities you see in the designer. You won’t need to modify this.
CS Mapping: The mapping between the above two models.
Designer content: Ignore this; it contains the designer surface details e.g. where on the screen the entities should be displayed etc.
So, you go into the CS Mapping section, and create a new scalar property in both Manager and Developer mapping fragments for ID e.g.
<EntityContainerMapping StorageEntityContainer="TestModelStoreContainer" CdmEntityContainer="TestEntities"> <EntitySetMapping Name="Employees"> <EntityTypeMapping TypeName="IsTypeOf(TestModel.Developer)"> <MappingFragment StoreEntitySet="Developer"> <ScalarProperty Name="Skill" ColumnName="Skill" /> <ScalarProperty Name="Age" ColumnName="Age" /> </MappingFragment> </EntityTypeMapping>
<EntityContainerMapping StorageEntityContainer="TestModelStoreContainer" CdmEntityContainer="TestEntities"> <EntitySetMapping Name="Employees"> <EntityTypeMapping TypeName="IsTypeOf(TestModel.Developer)"> <MappingFragment StoreEntitySet="Developer"> <ScalarProperty Name="Id" ColumnName="Id" /> <ScalarProperty Name="Skill" ColumnName="Skill" /> <ScalarProperty Name="Age" ColumnName="Age" /> </MappingFragment> </EntityTypeMapping>
- OK, let’s try to compile again. Still no luck – VS now complains about the lack of the mapping for the Name property, too! In other words:
you need to manually map across all shared properties from the base class to all derived classes
Once you create another mapping for Name (just like I did for ID), your code will compile, and you can do code like the following: –
var context = new TestEntities(); context.AddToEmployees(new Manager()); context.AddToEmployees(new Developer()); context.SaveChanges();
You can also query the Employees collection on the context in order to get all Employees, or do a Employees.OfType<Developer>().