Entity Framework Core 5 — Table-per-Type and Table-per-Hierarchy

A new feature that was added on Entity Framework Core 5, it’s the possibility to create a Table-per-Type (TPT) mapping. In this article, I explain the difference between the Table-per-Hierarchy (TPH) mapping and the Table-per-Type (TPT) mapping.

By default, EF Core maps an inheritance hierarchy of .NET types to a single table in the database, to store the data for all types in the hierarchy, and a discriminator column is used to identify which type each row represents, this is known as Table-per-Hierarchy (TPH) mapping.

With EF Core 5, it’s also possible to map each .NET type in an inheritance hierarchy to a different table, and this is known as Table-per-Type (TPT) mapping. With this new functionality, EF Core creates a base table in the database and create a specific table for each derived table.

To show the differences between the TPH and the TPT mapping, I’ve created a console application using .NET Core 5.0, and I’ve installed the packages:

  • Microsoft.EntityFrameworkCore.Design 5.0.3
  • Microsoft.EntityFrameworkCore.SqlServer 5.0.3
  • Microsoft.EntityFrameworkCore.Tools 5.0.3

On the Startup class, in the method OnConfiguring, I enabled the option to Log into the console all the queries generated by EF Core:

Table-per-Hierarchy (TPH) Mapping

For demonstration purpose, I’ve created three entities, and I will use them to explain the Table-per-Hierarchy (TPH) mapping and the Table-per-Type (TPT) mapping. These are the classes:

  • The class “Person” is the base class
  • The class “User” inherits from Person
  • The class “Client” also inherits from Person

I’ve also created the DbSets in the Startup class:

When the application run, this is the SQL Script that will be generated by EF Core:

A single table will be created, containing a field named “Discriminator”, and by this column, it is possible to discriminate between different types:

Now let’s create a LINQ query to return all the users:

The SQL query that will be generated for this query will be like this:

Note that in the SQL query, there is a filter to return all People where the Discriminator field is equal to “User”. This is the result of this query:

TPH Doesn’t Allow Null Values on Properties

Note that with this approach (TPH), it is not possible to have a required property in the derived entities (Client or User), because this is a shared table, and for this reason, all the properties in the derived entities need to allow null values. For example, if we set a configuration using DataAnnotations to the property Email in the Client class to be required, when the application is creating the database, an exception will be thrown:

Table-per-Type (TPT) Mapping

Now let’s see an example using table-per-type mapping. For this example, we will use the same three entities: Person (base class), User and Client (derived classes).

There are two ways to create a TPT mapping, and with this configuration, EF Core will create one table for each entity. One way of doing it is using the DataAnnotation in the name of the classes, for example:

Another way is configuring the tables using Fluent API:

When the configuration is made by Fluent API, it’s also necessary to add the configuration on the Startup class, for EF Core to apply the configuration from all IEntityTypeConfiguration<TEntity>. This configuration can be done using the “ApplyConfigurationsFromAssembly”, this way EF Core will do the discovery for all configurations which are implementing the IEntityTypeConfiguration:

When the application run, this is the SQL Script that will be generated by EF Core:

Note that now we have three tables, each one for each entity. Each table has its own properties, and there is a foreign key on the Clients and Users table, with reference to the People table.

Now let’s use the same LINQ query that was created before, to return all the users:

The SQL query that will be generated for this query will be like this:

Note that now in this SQL query, the searching is being made from the People table, with an INNER JOIN with the Users table. This is the result of this query:

TPT Allows Null Values on Properties

With TPT it’s possible to configure the properties in the derived class, in this example Client or Users, to be required, since now these entities will have their own tables in the database (it will be created different tables for each derived entity) and they don’t share the same table. So it’s now possible, for example, to set a configuration using DataAnnotations or configuring using Fluent API, to the property Email in the Client class to be required:

The table will be successfully created in the database, with the column “Email” configured to be required (“not null”):

Conclusion

With the Table-per-Hierarchy (TPH), EF Core will generate a single table, with a column to distinguish between the derived tables. With Table-per-Type (TPT) mapping, EF Core will generate different tables for each derived tables. An advantage of having a specific table for each devired entity, beyond having a better organization for the tables in the database, it’s the possibility to configure some property in the derived class to be required. This is something that is not possible when using TPH mapping, because with this approach the properties need to allow null values, once the base table is a shared table.

You can check the code of this project here:

Thanks for reading!

.NET Full-Stack Developer | C# | .NET | .NET CORE | ASP.NET MVC | Unit Test | Angular

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store