Inheritance Strategy in Entity Framework Core 5

Prior to .NET 3.5, we (developers) often used to write ADO.NET code or Enterprise Data Access Block to save or retrieve application data from the underlying database. We used to open a connection to the database, create a DataSet to fetch or submit the data to the database, convert data from the DataSet to .NET objects or vice-versa to apply business rules. This was a cumbersome and error prone process. Microsoft has provided a framework called “Entity Framework” to automate all these database related activities for your application. Entity Framework (EF) Core is a lightweight, extensible, open source and cross-platform version of Entity Framework data access technology.

  • Table per Type (TPT): This approach suggests a separate table for each domain class.
  • Table per Concrete Class (TPC): This approach suggests one table for one concrete class, but not for the abstract class. So, if you inherit the abstract class in multiple concrete classes, then the properties of the abstract class will be part of each table of the concrete class.

Table per Hierarchy (TPH)

By default, EF maps the inheritance using the table-per-hierarchy (TPH) pattern. TPH uses a single table to store the data for all types in the hierarchy, and a discriminator column is used to identify which type each row represents.

// tphUser is returned from the database.
context.Entry(tphUser).Property("Disicriminator").CurrentValue = someValue;
var users = context.TPHUsers
.OrderBy(b => EF.Property<string>(b, "Disicriminator"));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TPHUser>()
.HasDiscriminator<string>("UserType")
.HasValue<TPHTeacher>("Teacher")
.HasValue<TPHStudent>("Student");
}
Migration
Generated query
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TPHUser>()
.HasDiscriminator(b => b.UserType);
modelBuilder.Entity<TPHUser>()
.Property(e => e.UserType)
.HasMaxLength(200)
.HasColumnName("UserType");
}
Table created with table-per-hierarchy

Table per Type (TPT)

In the TPT mapping pattern, all the types are mapped to individual tables. Properties that belong solely to a base type or derived type are stored in a table that maps to that type. Tables that map to derived types also store a foreign key that joins the derived table with the base table.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TPTStudent>().ToTable("TPTStudents");
modelBuilder.Entity<TPTTeacher>().ToTable("TPTTeachers");
}
Table created with table-per-type

Table per Concrete Type (TPC)

As I mentioned earlier eventhough the table-per-concrete-type (TPC) is supported by EF6, is not yet supported by EF Core. It may not even come to EF Core 6, Shay Rojansky from Entity Framework team comented on the issue saying it probably will roll out in EF Core 7.

public DbSet<TPCStudent> TPCStudents { get; set; }
public DbSet<TPCTeacher> TPCTeachers { get; set; }

Performance

The choice of inheritance mapping technique can have a considerable impact on application performance — it’s recommended to carefully measure before committing to a choice.

Systems Design • Social Innovation • Cloud • ML