Introduction
Inheritance is a common concept in object-oriented programming. A base class can define common properties and behavior, while derived classes can add their own properties and functionality.
When using Entity Framework Core (EF Core), inheritance needs to be mapped from the object-oriented class hierarchy to relational database tables. EF Core supports several inheritance mapping strategies, including Table Per Hierarchy (TPH) and Table Per Type (TPT).
In this article, we will understand how TPH and TPT work in EF Core by creating a simple employee hierarchy. We will configure both strategies, insert sample data, query the entities, and understand how each approach is represented in the database.
What Is Inheritance Mapping in EF Core?
Consider a base Employee class with two derived classes:
public abstract class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class Developer : Employee
{
public string ProgrammingLanguage { get; set; } = string.Empty;
}
public class Manager : Employee
{
public int TeamSize { get; set; }
}
Here, Employee contains properties common to all employees. Developer and Manager inherit those properties and add their own specific properties.
The database does not directly understand C# inheritance. EF Core therefore needs an inheritance mapping strategy to determine how these classes should be stored in relational tables.
The two strategies discussed in this article are:
Table Per Hierarchy (TPH)
Table Per Type (TPT)
Create the Sample EF Core Project
Step 1: Create the Project
dotnet new console -n EfCoreInheritanceDemo
cd EfCoreInheritanceDemo
Step 2: Install Entity Framework Core Packages
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
The example uses SQL Server as the database provider.
Create the Entity Classes
Step 1: Create the Base Entity
public abstract class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
The Employee class contains the properties shared by all employee types.
Step 2: Create the Derived Entities
public class Developer : Employee
{
public string ProgrammingLanguage { get; set; } = string.Empty;
}
public class Manager : Employee
{
public int TeamSize { get; set; }
}
The Developer class contains the programming language specific to developers, while Manager contains the team size.
Create the DbContext
Create an EmployeeDbContext class:
using Microsoft.EntityFrameworkCore;
public class EmployeeDbContext : DbContext
{
public DbSet<Employee> Employees => Set<Employee>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
"Server=(localdb)\\MSSQLLocalDB;Database=EfCoreInheritanceDemo;Trusted_Connection=True;TrustServerCertificate=True");
}
}
The Employees DbSet represents the employee inheritance hierarchy.
The database structure generated by EF Core depends on the inheritance strategy configured in the model.
Table Per Hierarchy (TPH)
Table Per Hierarchy (TPH) stores the entire inheritance hierarchy in a single database table.
A discriminator column identifies which derived class each row represents.
For example, the database can conceptually look like this:
Employees
------------------------------------------------------------------
Id | Name | Discriminator | ProgrammingLanguage | TeamSize
------------------------------------------------------------------
1 | John | Developer | C# | NULL
2 | Sarah | Manager | NULL | 8
Configure TPH
TPH is the default inheritance strategy in EF Core. It can also be configured explicitly:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasDiscriminator<string>("Discriminator")
.HasValue<Developer>("Developer")
.HasValue<Manager>("Manager");
}
The Discriminator column identifies the CLR type represented by each database row.
Insert TPH Data
using (var context = new EmployeeDbContext())
{
context.Database.EnsureCreated();
var developer = new Developer
{
Name = "John",
ProgrammingLanguage = "C#"
};
var manager = new Manager
{
Name = "Sarah",
TeamSize = 8
};
context.Add(developer);
context.Add(manager);
context.SaveChanges();
}
Both entities are stored in the same Employees table.
Query TPH Data
using (var context = new EmployeeDbContext())
{
var employees = context.Employees.ToList();
foreach (var employee in employees)
{
Console.WriteLine(
$"{employee.Id} - {employee.Name} - {employee.GetType().Name}");
}
}
EF Core uses the discriminator value to determine whether each row should be materialized as a Developer or Manager.
TPH Output
1 - John - Developer
2 - Sarah - Manager
Advantages of TPH
Stores the entire hierarchy in one table.
Requires fewer joins when querying the hierarchy.
Keeps the database structure relatively simple.
Can provide good query performance for many inheritance scenarios.

Join the conversation! Your thoughts help the community grow.