How does EF Core map inheritance (TPH, TPT, TPC)?

Entity Framework & EF Core Modelling Advanced 3–5 Yrs5+ Yrs Conceptual 1 min read

Quick Answer

TPH (default) — one table for the whole hierarchy with a discriminator column; fast, but non-shared columns are nullable. TPT — a table per class with shared columns in the base table, joined on read; normalised but join-heavy. TPC (Core 7+) — a table per concrete class with all columns duplicated; no joins, no base table, but no shared FK target.

Table-per-hierarchy is the default and usually the right choice — the whole class hierarchy goes in one table with a discriminator column saying which type each row is. It's fast because there are no joins; the cost is that subtype-specific columns have to be nullable. Table-per-type gives each class its own table and joins them on every read, which is a nicer schema but slower. Table-per-concrete-class, added in EF Core 7, puts each concrete type in its own table with everything duplicated — fast polymorphic reads, but you lose a single FK target for the base type. I stick with TPH unless the nullable columns are a real problem.

  • TPH — best default: one query, one table. Downside: wide table, nullable columns for subtype-specific fields.
  • TPT — .UseTptMappingStrategy(). Cleaner schema, but every query joins all the tables — measurably slower.
  • TPC — .UseTpcMappingStrategy(). Great read performance for polymorphic queries, but you can't have a single foreign key referencing the abstract base.
  • Prefer TPH unless the nullable-column bloat is genuinely a problem.