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:
- 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.
🎬 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:
- 📘 Inheritance mapping (EF Core) — Microsoft Docs
- 📘 Table Per Type (TPT) — LearnEntityFrameworkCore
- 🎬 External Resources — Table Per Type (TPT)
🏁 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.
-
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
JOINsy
NULL,
aceptando duplicación.
