Skip to content

EF Core TPC Inheritance — Complete Guide

DodaTech Updated 2026-06-24 2 min read

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

You have a base Payment class with subclasses CreditCardPayment and PayPalPayment. You need to store them in separate tables — each table has all columns including the base class properties. TPC (Table-Per-Concrete-Type) mapping in EF Core creates one table per concrete class with all columns.

Wrong

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; }
}

Default TPH: All types in one Payments table with a discriminator column. CardNumber and Email columns are nullable.

Right (TPC)

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Payment>().UseTpcMappingStrategy()
        .ToTable("Payments");

    modelBuilder.Entity<CreditCardPayment>()
        .ToTable("CreditCardPayments");

    modelBuilder.Entity<PayPalPayment>()
        .ToTable("PayPalPayments");
}

Output: Three tables:

  • Payments (just Id, Amount) — can be abstract
  • CreditCardPayments (Id, Amount, CardNumber)
  • PayPalPayments (Id, Amount, Email)

Each table is complete — no sharing, no joins for concrete type queries.

Prevention

  • Use TPC when concrete types have different columns and you rarely query the base type.
  • Use TPC for better query performance on concrete types (no discriminator column, no joins).
  • Use TPC with abstract base entity when you never query the base type directly.
  • Use TPC when you need non-nullable columns per type (TPH would make them nullable).
  • Use TPH when you frequently query the base type across all subtypes.
  • Use TPT when you want normalized tables per type (base columns in one table, subtype columns in another).

Common Mistakes with core tpc inheritance

  1. Mixing let bindings with <- bindings in do notation, producing type errors
  2. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  3. Non-exhaustive pattern matches that compile with warnings then crash at runtime

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

What are the trade-offs of TPC?

TPC has no discriminator column and no joins for concrete types. However, querying the base type requires UNION ALL across all tables — expensive for many subclasses. Indexes must be created per table. Identity columns may conflict across tables.

Does TPC support relationships?

Yes. Navigation properties work with TPC. Foreign keys reference the concrete table. Relationships are mapped to the base type if the FK is shared, or to concrete types if specific.

How does Id generation work in TPC?

Each table has its own identity sequence. IDs can collide across tables — a CreditCardPayment and PayPalPayment may have the same Id. Use HiLo or Guid generation for unique IDs across all tables if needed.

TPC is used in DodaTech's payment processing for separate payment method tables. For more EF Core, visit DodaTech.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro