Table Per Type EF Core (TPT): herencia normalizada con JOINs

Table Per Type (TPT) en EF Core

Table Per Type EF Core (TPT): herencia normalizada con JOINs

Table Per Type (TPT) es una de las tres estrategias de herencia en Entity Framework Core, junto con TPH y TPC.

Con TPT, el tipo base y cada tipo derivado se almacenan en tablas separadas. Esto produce un esquema más normalizado, pero obliga a EF Core a combinar varias tablas para reconstruir cada entidad al consultar.

En esta guía verás cómo funciona Table Per Type en EF Core, cómo se configura, qué SQL genera y en qué escenarios conviene usarlo frente a TPH o TPC.

Si estás trabajando con jerarquías y relaciones reales entre entidades, también te puede servir esta guía sobre cómo relacionar tablas en EF Core con SQL Server.


🧠 ¿Qué hace exactamente Table Per Type EF Core (TPT)?

La forma más simple de entender Table Per Type es esta:

TPT = “Divide la jerarquía de herencia en varias tablas y usa JOINs para reconstruir cada entidad”.
  • El tipo base se almacena en su propia tabla.
  • Cada tipo derivado se almacena en una tabla independiente.
  • Las tablas derivadas se relacionan con la tabla base mediante claves primarias compartidas.
  • Las consultas a tipos derivados y al tipo base requieren JOINs.
  • EF Core materializa cada entidad combinando datos de varias tablas.

TPT encaja mejor cuando priorizas un esquema limpio y normalizado, aunque eso implique un SQL más pesado y mayor costo de lectura.


🧪 Demo interactiva: Table Per Type (TPT) en EF Core

Esta demo interactiva muestra cómo Entity Framework Core mapea una jerarquía de herencia con Table Per Type (TPT) y cómo cada entidad se reconstruye a partir de múltiples tablas relacionadas.

En el ejemplo se utiliza una jerarquía simple basada en Animal, con tipos derivados como Cat y Dog, donde cada tipo se almacena en su propia tabla. Al consultar un tipo derivado o la jerarquía completa, EF Core genera SELECTs con JOINs para materializar correctamente cada entidad.

🔗 Código completo en .NET Fiddle:
https://dotnetfiddle.net/G3aq5p

Puedes ejecutar el código, modificar el modelo y observar el comportamiento de TPT en tiempo real, incluyendo consultas sobre tipos derivados, consultas sobre el tipo base y el SQL generado con INNER JOIN y LEFT JOIN.


🧩 Reglas esenciales de Table Per Type (TPT) en EF Core

Antes de usar TPT en un proyecto real, conviene tener claras estas reglas:

  • El tipo base siempre tiene su propia tabla.
  • Cada tipo derivado se almacena en una tabla separada.
  • No existe columna discriminadora.
  • El tipo concreto se infiere a partir de las filas relacionadas.
  • Las consultas al tipo base suelen generar LEFT JOINs hacia todas las tablas derivadas.
  • Agregar un nuevo tipo derivado implica crear una nueva tabla, no añadir columnas nullable.
  • Todas las entidades de la jerarquía comparten la misma clave primaria.

TPT favorece la normalización del esquema, pero incrementa la complejidad del SQL y el costo de lectura a medida que la jerarquía crece.


💻 Ejemplo básico – Table Per Type EF Core (TPT)

Un caso típico de Table Per Type EF Core (TPT) combina una clase base, varios tipos derivados y un mapeo donde cada tipo se persiste en su propia tabla.

A diferencia de TPH, en TPT no existe discriminador: EF Core reconstruye cada entidad combinando la tabla base con la tabla derivada correspondiente.

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

⚙️ Configuración explícita de TPT (recomendado)

Aunque EF Core puede inferir parte del mapeo, en TPT conviene declarar la estrategia de forma explícita y definir con claridad la tabla base y las tablas derivadas.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Animal>()
        .UseTptMappingStrategy()
        .ToTable("Animals");

    modelBuilder.Entity<Cat>()
        .ToTable("Cats");

    modelBuilder.Entity<Dog>()
        .ToTable("Dogs");
}

Nota: en TPT, el tipo base se almacena en su propia tabla incluso si es abstracto.


🧾 SQL generado (consulta por tipo derivado)

var cats = context.Set<Animal>()
    .OfType<Cat>()
    .ToList();
SELECT [a].[Id], [a].[Name], [a].[DateOfBirth], [c].[IsIndoor], [c].[LivesRemaining]
FROM [Animals] AS [a]
INNER JOIN [Cats] AS [c] ON [a].[Id] = [c].[Id];

🧾 SQL generado (consulta polimórfica al tipo base)

Cuando consultas el tipo base en una jerarquía mapeada con TPT, EF Core necesita combinar la tabla base con todas las tablas derivadas.

Eso suele traducirse en un SELECT con múltiples LEFT JOINs, incluso si la consulta no accede directamente a propiedades de los tipos derivados.

var animals = context.Animals.ToList();
SELECT
    [a].[Id],
    [a].[Name],
    [a].[DateOfBirth],
    [c].[IsIndoor],
    [c].[LivesRemaining],
    [d].[Breed],
    [d].[IsGoodBoy]
FROM [Animals] AS [a]
LEFT JOIN [Cats] AS [c] ON [a].[Id] = [c].[Id]
LEFT JOIN [Dogs] AS [d] ON [a].[Id] = [d].[Id];

Este patrón es una de las principales razones por las que TPT puede volverse costoso en lectura cuando la jerarquía aumenta.


⚖️ TPH vs TPT vs TPC (comparativa rápida)

En EF Core existen tres estrategias de herencia. La diferencia entre ellas está en el esquema, el SQL típico y el trade-off principal de cada una.

Estrategia Esquema SQL típico Trade-off principal
TPH Una sola tabla SELECT con filtro por discriminador Tabla ancha + columnas NULL
TPT Tabla base + tablas derivadas JOINs (INNER/LEFT JOIN) SQL más complejo + mayor costo de lectura
TPC Una tabla por tipo concreto UNION ALL Duplicación de columnas

✅ ¿Cuándo conviene usar TPT y cuándo evitarlo?

TPT suele ser una buena opción cuando la normalización del esquema es prioritaria, los tipos derivados tienen diferencias reales entre sí y el costo adicional de lectura sigue siendo aceptable para el sistema.

En cambio, conviene evitarlo cuando el rendimiento de lectura es crítico, cuando las consultas polimórficas son frecuentes o cuando quieres mantener un SQL más simple.

Regla práctica: elige TPT si valoras más un esquema limpio y normalizado que el costo extra de los JOINs.

🎬 Recursos recomendados

Si quieres profundizar en Table Per Type (TPT) y compararlo mejor con TPH y TPC, estos recursos son los más útiles para continuar:

Tip: guarda estos enlaces para tener a mano documentación oficial y referencias rápidas cuando estés decidiendo una estrategia de herencia según tus patrones de consulta.

🏁 Conclusión

Table Per Type EF Core (TPT) es una estrategia pensada para escenarios donde la normalización del esquema pesa más que el costo adicional de lectura. Puede ser una buena elección cuando la jerarquía está bien definida y el uso de JOINs no compromete el rendimiento esperado del sistema.

En resumen:

  • Usa TPT si priorizas un esquema limpio y normalizado, aunque eso implique más
    JOINs.
  • Evalúa TPH si necesitas mejor rendimiento de lectura y un SQL más simple.
  • Evalúa TPC si quieres evitar
    JOINs y
    NULL,
    aceptando duplicación.
Scroll to Top