Inheritance strategies in EF Core

Bogdan Hatis
3 min readJan 15, 2023

In Entity Framework Core (EF Core), inheritance is a powerful feature that allows you to model a hierarchical relationship between your entities. By default, EF Core uses the “Table per Hierarchy” (TPH) strategy to handle inheritance, but it also supports “Table per Type” (TPT) and “Table per Concrete Type” (TPC) strategies. In this article, we will discuss these three strategies in detail and provide examples of how to implement them in your EF Core application.

Table per Hierarchy (TPH)

The TPH strategy is the default inheritance strategy in EF Core. It stores all classes in the hierarchy in a single table, with a discriminator column used to distinguish between the different types. The discriminator column is used to determine the type of an entity when it is retrieved from the database.

For example, let’s say we have a base class called “Animal” and two derived classes called “Cats” and “Dogs”. The TPH strategy would create a single table called “Animals” with columns for the common properties of all animals, as well as a discriminator column that would contain the string “Cat” or “Dog” to indicate the type of animal.

Here is an example of how to implement TPH in EF Core:

protected override voidOnModelCreating(ModelBuilder modelBuilder) 
{
modelBuilder.Entity<Animal>()
.HasDiscriminator<string>("Type")
.HasValue<Cats>("Cat")
.HasValue<Dogs>("Dog");
}

Table per Type (TPT)

The TPT strategy creates a separate table for each class in the hierarchy and uses foreign keys to link them together. The foreign key is used to link the derived class to the base class.

For example, if we have a base class called “Animals” and two derived classes called “Cats” and “Dogs” then TPT strategy would create two tables, one called “Cats” and one called “Dogs”. “Cats” table would have columns for the properties specific to cats and a foreign key linking it to the “Animals” table, and “Dogs” table would have columns for the properties specific to dogs and a foreign key linking it to the “Animals” table.

Here is an example of how to implement TPT in EF Core:

protected override voidOnModelCreating(ModelBuilder modelBuilder) 
{
modelBuilder.Entity<Animals>()
.ToTable("Animals");

modelBuilder.Entity<Cats>()
.ToTable("Cats")
.HasOne(c => c.Animal)
.WithMany(a => a.Cats)
.HasForeignKey(c => c.AnimalId);

modelBuilder.Entity<Dogs>()
.ToTable("Dogs")
.HasOne(d => d.Animals)
.WithMany(a => a.Dogs)
.HasForeignKey(d => d.AnimalsId);
}

Table per Concrete Type (TPC)

The TPC strategy creates a separate table for each non-abstract class in the hierarchy, with no foreign keys linking them.

For example, if we have a base class called “Animals” and two derived classes called “Cats” and “Dogs” then TPC strategy would create two tables, one called “Cats” and one called “Dogs”. “Cats” table would have columns for the properties specific to cats and “Dogs” table would have columns for the properties specific to dogs. There would be no foreign key linking the two tables.

Here is an example of how to implement TPC in EF Core:

protected override voidOnModelCreating(ModelBuilder modelBuilder) 
{
modelBuilder.Entity<Animals>()
.HasDiscriminator(a => a.Discriminator)
.HasValue<Cats>("Cat")
.HasValue<Dogs>("Dog");

modelBuilder.Entity<Cats>()
.ToTable("Cats")

modelBuilder.Entity<Dogs>()
.ToTable("Dogs")
}

In conclusion, each inheritance strategy has its own advantages and disadvantages, and the best strategy to use depends on your specific requirements. TPH is the most flexible strategy and is easy to implement, but it can result in a large table with many nullable columns. TPT can result in a more normalized database and can provide better performance for certain queries, but it can be more complex to implement and maintain. TPC is the simplest strategy and can provide better performance for certain queries, but it can result in a less normalized database and can make it more difficult to implement certain features such as lazy loading.

When choosing the right inheritance strategy, it’s important to consider the trade-offs and pick the one that best fits your application’s requirements. It’s also possible to use different inheritance strategies for different parts of your model, depending on your requirements.

Originally published at https://bogdanhatis.com on January 15, 2023.

--

--

Bogdan Hatis

Experienced developer, product manager and CTO with a demonstrated history of working in the information technology, services and fintech industry.