EF Core provides three inheritance mapping strategies:
This page focuses on Table Per Hierarchy (TPH).
What is Table Per Hierarchy (TPH) in EF Core
Table Per Hierarchy (TPH) is the default inheritance mapping strategy in Entity Framework Core.
EF Core represents an object-oriented inheritance hierarchy using a single database table, where a discriminator column identifies the concrete derived type for each row.
All entities in the hierarchy share the same table structure, with unused columns set to NULL depending on the concrete type. Adding new derived types typically results in additional nullable columns being added to the same table.

TL;DR - EF Core TPH
- Uses one table for the entire inheritance hierarchy
- Relies on a discriminator column to identify derived types
- No JOINs when querying derived entities
- Generally provides strong read performance
- Results in wide tables with nullable columns
- Best suited for stable hierarchies with limited derived types
Quick Mental Model
Think of TPH as one large table storing all entities in an inheritance hierarchy.
The discriminator column tells EF Core how to materialize each row into the correct CLR type.
You gain performance by avoiding JOINs in polymorphic queries, but you trade schema normalization for simplicity and speed.
Schema Shape
- A single table represents the entire hierarchy
- Columns include:
- Base class properties
- All derived-type specific properties
- A discriminator column identifies the concrete type
- Many columns may be
NULLdepending on the row type
Configuration TPH in EF Core
The Model
The following model is used consistently across all inheritance strategies:
public abstract class Animal
{
public int Id { get; set; }
public string? Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class Cat : Animal
{
public bool IsIndoor { get; set; }
public int LivesRemaining { get; set; }
}
public class Dog : Animal
{
public string? Breed { get; set; }
public bool IsGoodBoy { get; set; }
}
Configuring TPH Mapping
Although TPH is the default strategy, we always recommend configuring it explicitly by using UseTphMappingStrategy() to avoid any ambiguity for other developers:
public class AnimalsDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>(entity =>
{
entity.UseTphMappingStrategy();
entity
.HasDiscriminator<int>("AnimalType")
.HasValue<Cat>(1)
.HasValue<Dog>(2);
});
base.OnModelCreating(modelBuilder);
}
public DbSet<Animal> Animals { get; set; }
}
The discriminator column tells EF Core which concrete CLR type should be materialized for each row.
Usually, only the base set (Animals) is exposed.
TPH Generated Database Schema
With this configuration, EF Core generates only one table for the entire inheritance hierarchy:
CREATE TABLE [Animals] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[DateOfBirth] datetime2 NOT NULL,
[AnimalType] int NOT NULL,
[IsIndoor] bit NULL,
[LivesRemaining] int NULL,
[Breed] nvarchar(max) NULL,
[IsGoodBoy] bit NULL,
CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);
The table includes the base class properties and all derived-type specific properties (stored as nullable columns).
TPH Retrieving Derived Types via DbSet
To retrieve derived types, you normally query the root Animals set and use OfType<T>() to retrieve a specific derived type.
public class AnimalsDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>().UseTphMappingStrategy();
modelBuilder.Entity<Cat>();
modelBuilder.Entity<Dog>();
base.OnModelCreating(modelBuilder);
}
public DbSet<Animal> Animals { get; set; }
}
using (var context = new AnimalsDbContext())
{
var cats = context.Animals
.OfType<Cat>()
.ToList();
var dogs = context.Animals
.OfType<Dog>()
.ToList();
}
This is the traditional way to retrieve derived types when only the root DbSet is exposed.
In addition to OfType<T>(), EF Core also allows querying derived entities directly by exposing DbSet properties for concrete types.
public class AnimalsDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>().UseTphMappingStrategy();
base.OnModelCreating(modelBuilder);
}
public DbSet<Animal> Animals { get; set; }
public DbSet<Cat> Cats { get; set; }
public DbSet<Dog> Dogs { get; set; }
}
using (var context = new AnimalsDbContext())
{
var cats = context.Cats.ToList();
var dogs = context.Dogs.ToList();
}
This approach is less traditional but often simpler and more expressive, especially when working primarily with concrete types.
SQL Behavior
- Queries against derived types are translated into single-table SELECTs
- Filtering is performed using the discriminator column
- No JOINs are generated
- All data is retrieved from one table
Generated SQL
The following examples show a simplified version of the SQL typically generated by EF Core for the TPH mapping strategy.
-- Querying the root type (context.Animals)
SELECT [a].[Id],
[a].[AnimalType],
[a].[DateOfBirth],
[a].[Name],
[a].[IsIndoor],
[a].[LivesRemaining],
[a].[Breed],
[a].[IsGoodBoy]
FROM [Animals] AS [a]
-- Querying only Cats (context.Animals.OfType<Cat>() / context.Cats)
SELECT [a].[Id],
[a].[AnimalType],
[a].[DateOfBirth],
[a].[Name],
[a].[IsIndoor],
[a].[LivesRemaining]
FROM [Animals] AS [a]
WHERE [a].[AnimalType] = 1
-- Querying only Dogs (context.Animals.OfType<Dog>() / context.Dogs)
SELECT [a].[Id],
[a].[AnimalType],
[a].[DateOfBirth],
[a].[Name],
[a].[Breed],
[a].[IsGoodBoy]
FROM [Animals] AS [a]
WHERE [a].[AnimalType] = 2
Performance Characteristics
- Reads are typically very efficient due to single-table access
- Writes are simple and fast
- Trade-offs include:
- Wide tables
- Many nullable columns
- Schema changes when adding new derived types
Compared to other inheritance strategies, TPH usually offers the best query performance for polymorphic workloads, since it avoids JOINs entirely.
TPH Common Use Cases
TPH is commonly used when:
- Query performance matters more than normalization
- The inheritance hierarchy is stable
- Most derived types share a significant number of common properties
When to Use vs When NOT to Use TPH
Use TPH when:
- Polymorphic queries are common
- Performance is critical
- The number of derived types is limited
- Schema simplicity is preferred
Avoid TPH when:
- The hierarchy contains many optional properties
- New derived types are added frequently
- Table width becomes difficult to manage
External Resources — Table Per Hierarchy (TPH)
The following resources provide deeper insight into EF Core inheritance mapping, including internal design decisions, SQL behavior, and performance trade-offs.
They are especially useful for understanding why TPH is the default strategy, how it compares to TPT and TPC, and what practical implications each approach has in real-world systems.
Video 1 — .NET Data Community Standup
TPH, TPT, and TPC Inheritance Mapping with EF Core
Arthur Vickers explains how EF Core maps inheritance hierarchies internally, with a strong focus on SQL generation, performance characteristics, and storage trade-offs. This talk provides essential context for understanding why TPH often delivers the best query performance.
Key sections:
- 09:00 — TPH concept and discriminator column
- 15:00 — Queries without JOINs
- 26:30 — Performance comparison: TPT vs TPH
- 42:00 — Sparse columns optimization (SQL Server)
Video 2 — EF Core Inheritance: TPH vs TPT vs TPC with Real Examples
Remigiusz demonstrates inheritance mapping using real models and migrations, highlighting common configuration pitfalls. The TPH section clearly shows how a single-table strategy simplifies queries while introducing nullable columns as a trade-off.
Key sections:
- 03:08 — Single table with discriminator column
- 04:31 — Drawback: many NULL columns
- 06:31 — Explicit
HasDiscriminator()andHasValue()configuration - 09:31 — Abstract base class and discriminator configuration errors
Video 3 — Entity Framework 7 — Inheritance
Jasper provides a high-level overview of inheritance strategies in EF Core 7, showing how TPH is applied by default and how discriminator columns affect the generated schema and query behavior.
Key sections:
- 04:30 — Default TPH mapping (single table)
- 05:00 — Discriminator column and NULL values
- 11:45 — Explicit
UseTphMappingStrategy()configuration - 12:30 — Conclusions and EF Core 7 considerations
Summary & Next Steps
Table Per Hierarchy (TPH) is the simplest and most performant inheritance strategy in EF Core.
It excels at read-heavy, polymorphic workloads but trades normalization for speed and simplicity.
Next steps:
- Explore Table Per Type (TPT) for normalized schemas
- Explore Table Per Concrete Type (TPC) for write-optimized scenarios
- Review a side-by-side comparison of all inheritance strategies in EF Core
Each inheritance strategy (TPH, TPT, TPC) optimizes for different trade-offs between performance, normalization, and schema flexibility.