EF CTP4 Tips & Tricks: Code First Inheritance Mapping

This is post #7 in a series about the recently released Entity Framework Feature CTP4, also known as “EF Magic Unicorn Edition”.

In this post we are going to take a look at different options for the shape of the table(s) that store entities in an inheritance hierarchy.

Disclaimer: The Fluent API for mapping to a database schema, inheritance mapping in particular, is pretty horrible definitely not intuitive or concise in CTP4 and is something our team is currently working to improve. (i.e. this code is all going to change, for the better, in the next release)

The Model

First lets take a quick look at the model we are going to use for this post, it’s pretty simple and has two classes that participate in an inheritance hierarchy. We’ve then defined a derived DbContext and overridden the OnModelCreating method so that we can use the Fluent API to customize our mapping.

public class ProductContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // TODO: Insert Custom Mapping Code
    }
}

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal UnitPrice { get; set; }
}

public class DiscontinuedProduct : Product
{
    public DateTime DiscontinuedDate { get; set; }
}

Default (TPH)

Of course we aren’t forced to explicitly do any mapping to be able to persist data, if we chose not to do anything then Code First will default to use the Table Per Hierarchy (TPH) strategy. TPH basically equates to “store all my data in one table and use the values from one or more columns to identify which type each row is”. By default Code First will create a separate ‘Discriminator’ column to identify which type each row is, the class name is used as the value in this discriminator column.

Here is what our default schema looks like:

Default_TPH_Schema

And here is some sample data from the table:

Default_TPH_Data

Table Per Hierarchy (TPH)

We can also explicitly configure TPH so that we have more control over column names etc. Here we are specifying that the discriminator column is called ‘Type’ and the value for Product is ‘P’ and DiscontinuedProduct is ‘D’.

public class ProductContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .MapHierarchy()
            .Case<Product>(p => new
            {
                p.ProductId,
                p.Name,
                p.UnitPrice,
                Type = "P"
            })
            .Case<DiscontinuedProduct>(d => new
            {
                d.DiscontinuedDate,
                Type = "D"
            })
            .ToTable("dbo.Products");
    }
}

Our schema now looks like this:

Custom_TPH_Schema

And our sample data looks like this:

Custom_TPH_Data

Table Per Type (TPT)

Table Per Type basically equates to “store all the data for properties on the base type in a single table, store any additional data for derived types in an extra table that has a foreign key to the base table”. Here is the code:

public class ProductContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .MapHierarchy(p => new
            {
                p.ProductId,
                p.Name,
                p.UnitPrice,
            })
            .ToTable("dbo.Products");

        modelBuilder.Entity<DiscontinuedProduct>()
            .MapHierarchy(d => new
            {
                d.ProductId,
                d.DiscontinuedDate,
            })
            .ToTable("dbo.DiscontinuedProducts");
    }
}

Our schema now looks like this:

TPT_Schema

And our sample data looks like this:

TPT_Data

Table Per Concrete Class (TPC)

TPC means “Create a completely separate table for each non-abstract type in my hierarchy”. The code for this is shown below, note that because there is no foreign key between the two tables we need to take care of providing unique keys, therefore we are switching off identity on the primary key property.

public class ProductContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .Property(p => p.ProductId)
            .StoreGeneratedPattern = StoreGeneratedPattern.None;

        modelBuilder.Entity<Product>()
            .MapSingleType(p => new
            {
                p.ProductId,
                p.Name,
                p.UnitPrice,
            })
            .ToTable("dbo.Products");

        modelBuilder.Entity<DiscontinuedProduct>()
            .MapSingleType(d => new
            {
                d.ProductId,
                d.Name,
                d.UnitPrice,
                d.DiscontinuedDate,
            })
            .ToTable("dbo.DiscontinuedProducts");
    }
}

Our schema now looks like this:

TPC_Schema

And our sample data looks like this:

TPC_Data

Summary

By default Code First will map inheritance hierarchies to a single table using the TPH pattern, you can tweak the default TPH mapping or swap to TPT or TPC using the Fluent API. The API calls to achieve the various mappings in CTP4 are pretty verbose and not very discoverable… but it will be better in the next release… stay tuned 🙂

Advertisements