Skip to content

EF Core TPT Inheritance — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about EF Core TPT Inheritance. We cover key concepts, practical examples, and best practices.

You want each type in your hierarchy to have its own table — the base type in one table, each subclass in a separate table with only its unique properties, sharing the primary key with the base table. TPT creates a normalized table structure.

Configuration

public class Payment
{
    public int Id { get; set; }
    public decimal Amount { get; set; }
}

public class CreditCardPayment : Payment
{
    public string CardNumber { get; set; }
}

public class PayPalPayment : Payment
{
    public string Email { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Payment>().ToTable("Payments");
    modelBuilder.Entity<CreditCardPayment>().ToTable("CreditCardPayments");
    modelBuilder.Entity<PayPalPayment>().ToTable("PayPalPayments");
}

Output: Three tables:

  • Payments (Id, Amount)
  • CreditCardPayments (Id, CardNumber) — Id is FK and PK referencing Payments
  • PayPalPayments (Id, Email) — Id is FK and PK referencing Payments

Querying a CreditCardPayment joins Payments + CreditCardPayments.

Prevention

  • Use TPT for a normalized schema where base and derived types are in separate tables.
  • Use TPT when you need non-nullable columns per type without nullable columns in the base table.
  • Use TPH by default — TPT has query performance overhead due to joins.
  • Use TPC instead of TPT when you rarely query the base type.
  • Add indexes on foreign key columns in subtype tables.
  • Be aware that TPT always requires a join for any query on a derived type.
  • Use TPT only when the schema normalization benefit outweighs the query overhead.

Common Mistakes with core tpt inheritance

  1. Using head and tail instead of pattern matching, causing runtime errors on empty lists
  2. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  3. Using return to exit a function early instead of wrapping a pure value in the monad

These mistakes appear frequently in real-world EF code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

Does TPT support polymorphic queries?

Yes. Querying db.Payments returns all types via left joins. Querying db.CreditCardPayments returns only that type via a join. The base type query can be expensive with many subtypes because it joins all subtype tables.

What are the performance implications of TPT?

Every query on a derived type requires a JOIN between the base table and the derived table. Queries on the base type require LEFT JOIN to all derived tables. For hierarchies with many subtypes or large tables, this can be significantly slower than TPH.

Can I mix TPT and TPH in the same hierarchy?

No. A single hierarchy must use one strategy. You cannot have some subclasses in separate tables while others share the base table. Choose one strategy per hierarchy and apply it to all types in that hierarchy.

TPT is used in DodaTech's compliance module where schema normalization is required by audit regulations. For more EF Core, visit DodaTech.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro