Back to: ASP.NET Core Tutorials For Beginners and Professionals
Entity Framework Core Inheritance (TPH, TPT, and TPC)
In this article, I will discuss Entity Framework Core (EF Core) Inheritance (TPH, TPT, and TPC) with Examples. Please read our previous article discussing Stored Procedures in Entity Framework Core with Examples. At the end of this article, you will understand the following pointers:
- What do you mean by Entity Framework Core Inheritance?
- Table Per Hierarchy (TPH) Inheritance in Entity Framework Core
- Example to Understand Table Per Hierarchy (TPH) in EF Core
- Table Per Type (TPT) Inheritance in Entity Framework core
- Example to Understand Table Per Type (TPT) in EF Core
- Table Per Concrete Type (TPC) Inheritance in Entity Framework Core
- Example to Understand Table Per Concrete Type (TPC) Inheritance in EF Core
- TPH vs. TPT vs. TPC in EF Core
- How to Choose the Right Strategy?
What do you mean by Entity Framework Core Inheritance?
Inheritance is one of the Object-Oriented Programming (OOP) Principles where a class (known as a Derived, Child, or Subclass) can inherit properties and methods from another class (known as a Base, Parent, or Child class). This allows code reusability functionality.
Entity Framework Core supports inheritance, allowing you to map an inheritance hierarchy of .NET classes to a relational database. EF Core supports three common inheritance mapping patterns:
- Table Per Hierarchy (TPH) – 1 Table Per Hierarchy
- Table Per Type (TPT) – 1 Table Per Type
- Table Per Concrete Class (TPC) – 1 Table Per Each Concrete Class, not for Abstract classes or Interfaces
Table Per Hierarchy (TPH) Inheritance in Entity Framework Core:
Table Per Hierarchy (TPH) is called Single-Table Inheritance. In this strategy, all the classes in the inheritance hierarchy are stored in a single database table. There’s a discriminator column that EF Core uses to differentiate between the types.
- This is the default inheritance mapping strategy in EF Core.
- A single table stores the data for all types in the hierarchy.
- The table includes columns for all properties of all classes in the hierarchy.
- A discriminator column is used to identify the type of each row.
Example to Understand Table Per Hierarchy (TPH) in EF Core
Table Per Hierarchy (TPH) is one of the inheritance strategies available in Entity Framework Core (EF Core). This can be a more efficient and straightforward approach when compared to other inheritance strategies like Table-Per-Type (TPT) or Table-Per-Concrete-Type (TPC). Here’s how we can implement Table-Per-Hierarchy inheritance in EF Core:
Base Class:
We must start by defining a base class representing the common properties and fields for all the entities in our inheritance hierarchy. So, create the following Base Entity.
namespace EFCoreCodeFirstDemo.Entities { public class BaseEntity { public int Id { get; set; } public string CommonProperty { get; set; } } }
Derived Classes:
Next, we must create derived classes that inherit from the base class and represent the specialized entities in your hierarchy. Each derived class can have additional properties specific to that entity type. So, create the following Derived Entities:
namespace EFCoreCodeFirstDemo.Entities { public class DerivedEntityA : BaseEntity { public string PropertyA { get; set; } } public class DerivedEntityB : BaseEntity { public string PropertyB { get; set; } } }
TPH Configuration in EF Core:
In EF Core, you must configure TPH inheritance using Fluent API in your DbContext class. Here, we need to specify that all entities in the hierarchy should be stored in a single database table and use a discriminator column to differentiate between different entity types. Modify the DbContext class as follows:
using Microsoft.EntityFrameworkCore; namespace EFCoreCodeFirstDemo.Entities { public class EFCoreDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { //Configuring the Connection String optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertificate=True;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BaseEntity>() .ToTable("Entities") .HasDiscriminator<string>("entity_type") .HasValue<DerivedEntityA>("EntityA") .HasValue<DerivedEntityB>("EntityB"); } public DbSet<BaseEntity> BaseEntites { get; set; } } }
Points to Remember:
- Discriminator Column: EF Core will add a discriminator column to the Entities database table named entity_type, which we configured using the HasDiscriminator method. This column will store a value that indicates the type of entity each row represents. The discriminator column, i.e., entity_type column, will contain EntityA or EntityB, which we configured using the HasValue Method.
- Querying: When we query the database, EF Core automatically uses the discriminator column to determine the entity type and return the appropriate derived class instances.
- Inserting and Updating: When we insert or update entities, EF Core will set the discriminator value appropriately based on the type of entity we are working with.
With the above changes, open the Package Manager Console and Execute the add-migration and update-database commands as follows. You can give any name to your migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be given earlier.
Now, if you verify the database, then you will see the following:
Modify the Program class to check whether TPH Inheritance works as expected. The following example code is self-explained, so please go through the comment lines for a better understanding:
using EFCoreCodeFirstDemo.Entities; namespace EFCoreCodeFirstDemo { public class Program { static async Task Main(string[] args) { try { //Use Inheritance in our Code //Now, you can create and work with instances of the derived classes //and save them to the database using (var context = new EFCoreDbContext()) { var derivedEntityA = new DerivedEntityA { PropertyA = "SomeValueA", CommonProperty = "SomeCommonValue" }; var derivedEntityB = new DerivedEntityB { PropertyB = "SomeValueB", CommonProperty = "SomeCommonValue" }; context.BaseEntites.AddRange(derivedEntityA, derivedEntityB); context.SaveChanges(); Console.WriteLine("Entities are Added"); } //Query the Inheritance Hierarchy //You can query the inheritance hierarchy using LINQ queries using (var context = new EFCoreDbContext()) { var baseEntities = context.BaseEntites.ToList(); foreach (var vehicle in baseEntities) { if (vehicle is DerivedEntityA derivedEntityA) { Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntityA.Id}, PropertyA: {derivedEntityA.PropertyA}, CommonProperty: {derivedEntityA.CommonProperty}"); } else if (vehicle is DerivedEntityB derivedEntityB) { Console.WriteLine($"\tDerivedEntityB: Id: {derivedEntityB.Id}, PropertyA: {derivedEntityB.PropertyB}, CommonProperty: {derivedEntityB.CommonProperty}"); } } } Console.Read(); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); ; } } } }
Output:
If you verify the database table, you will see the following data.
Performance Considerations: The table can become wide with many nullable columns, especially if the inheritance hierarchy is large with many properties. Consider the performance implications when using TPH, especially when dealing with many derived types with many unique properties.
So, the above example demonstrates how to implement Table-Per-Hierarchy (TPH) inheritance in EF Core to store related entities in a single database table while preserving the type information.
Table Per Type (TPT) Inheritance in Entity Framework core:
Table Per Type (TPT) is also known as Class Table Inheritance. In this strategy, each class in the inheritance hierarchy has its own corresponding database table (database tables for both base/parent and derived/child classes). Common properties are stored in a shared table, while specific properties for each derived class are stored in their respective tables. EF Core automatically creates relationships between the shared table and derived tables.
- Each class in the hierarchy is mapped to a separate table.
- Properties that belong to the base class are stored in the base table, and properties specific to derived classes are stored in their own tables.
- Relationships between tables reflect the inheritance structure and are usually implemented using primary and foreign keys.
- The Base Table Primary Key will be created as the Primary Key and Foreign Key in the Child Tables.
Example to Understand Table Per Type (TPT) in EF Core:
In TPT, each class in the inheritance hierarchy is mapped to its own table in the database. This approach can be useful when you clearly distinguish between base and derived entities and don’t want to mix derived entity properties in the base table. Here’s how you can implement TPT inheritance in EF Core:
Define Your Base Class:
Start by defining a base class representing the common properties and fields shared by all the classes in your inheritance hierarchy. This base class should be decorated with the [Table] attribute to specify the table name explicitly and with [Key] to indicate the primary key. This is the parent class in our model. It will be represented by its own table in the database. So, create the following Base Entity.
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace EFCoreCodeFirstDemo.Entities { [Table("BaseTable")] public class BaseEntity { [Key] public int Id { get; set; } public string CommonProperty { get; set; } } }
Define Your Derived Classes:
Each derived class will also be represented by its own table in the database. The table for a derived class includes a primary key column (this will be the Primary Key Column of the Base table) that is also a foreign key referencing the base class table. Create the derived classes that inherit from the base class. Each derived class should have its own properties and fields, and they can be decorated with [Table] attributes to specify their table names.
using System.ComponentModel.DataAnnotations.Schema; namespace EFCoreCodeFirstDemo.Entities { [Table("DerivedTable1")] public class DerivedEntity1 : BaseEntity { public string Property1 { get; set; } } [Table("DerivedTable2")] public class DerivedEntity2 : BaseEntity { public string Property2 { get; set; } } }
Configure the Model:
In your DbContext class, use the modelBuilder to configure the TPT inheritance strategy using the HasBaseType method.
using Microsoft.EntityFrameworkCore; namespace EFCoreCodeFirstDemo.Entities { public class EFCoreDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { //Configuring the Connection String optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertificate=True;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<DerivedEntity1>().HasBaseType<BaseEntity>(); modelBuilder.Entity<DerivedEntity2>().HasBaseType<BaseEntity>(); } public DbSet<BaseEntity> BaseEntites { get; set; } } }
Migrate and Apply Database Changes:
After defining your entities and configuring the model, you can use EF Core’s migration commands to create or update the database schema based on your model. So, open the Package Manager Console and Execute the add-migration and update-database commands as follows. You can give any name to your migration. Here, I am giving Mig1. The name that you are giving it should not be given earlier.
Now, if you verify the database, then you will see the following:
Modify the Program class to check whether TPT Inheritance is working as expected. The following example code is self-explained, so please go through the comment lines for a better understanding:
using EFCoreCodeFirstDemo.Entities; namespace EFCoreCodeFirstDemo { public class Program { static async Task Main(string[] args) { try { //Use Inheritance in our Code //Now, you can create and work with instances of the derived classes //and save them to the database using (var context = new EFCoreDbContext()) { var derivedEntityA = new DerivedEntity1 { Property1 = "SomeValue1", CommonProperty = "SomeCommonValue" }; var derivedEntityB = new DerivedEntity2 { Property2 = "SomeValue2", CommonProperty = "SomeCommonValue" }; context.BaseEntites.AddRange(derivedEntityA, derivedEntityB); context.SaveChanges(); Console.WriteLine("Entities are Added"); } //Query the Inheritance Hierarchy //You can query the inheritance hierarchy using LINQ queries using (var context = new EFCoreDbContext()) { var baseEntities = context.BaseEntites.ToList(); foreach (var vehicle in baseEntities) { if (vehicle is DerivedEntity1 derivedEntityA) { Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntityA.Id}, Property1: {derivedEntityA.Property1}, CommonProperty: {derivedEntityA.CommonProperty}"); } else if (vehicle is DerivedEntity2 derivedEntityB) { Console.WriteLine($"\tDerivedEntityB: Id: {derivedEntityB.Id}, Property2: {derivedEntityB.Property2}, CommonProperty: {derivedEntityB.CommonProperty}"); } } } Console.Read(); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); ; } } } }
Output:
If you verify the database table, you will see the following data.
Considerations for TPT
- Performance: TPT internally uses joins to construct the entity. So, querying for derived types can be slower than Table-Per-Hierarchy (TPH), which uses a single table to store the entire inheritance hierarchy.
- Schema Complexity: TPT can result in a more complex database schema if you have many derived types, as each requires its own table.
- Data Integrity: Using foreign keys in TPT helps maintain referential integrity across the different tables in the inheritance hierarchy.
Table Per Concrete Type (TPC) Inheritance in Entity Framework Core:
Table Per Concrete Type/Class (TPC) is a strategy where each concrete class in the inheritance hierarchy has its own database table, and there is no shared table for common properties. EF Core doesn’t automatically create relationships between tables in TPC.
- Each non-abstract class is mapped to its own table.
- All properties, including those inherited, are mapped to columns in the database table.
- This approach can lead to redundancy if the hierarchy is deep and classes share many properties.
Example to Understand Table Per Concrete Type (TPC) Inheritance in EF Core
TPC is a pattern where each concrete class in the inheritance hierarchy is mapped to its own table. Each table contains the columns for the class properties, including inherited properties. Unlike Table-Per-Hierarchy (TPH), where a single table includes data for all types in the hierarchy, or Table-Per-Type (TPT), where each type has its own table but only stores data for the properties declared at that level, TPC has separate tables that are not connected through foreign keys and each table repeats the columns for the inherited properties. This strategy is useful when avoiding joins and efficiently retrieving data for a specific derived type. Here’s how you can implement TPC inheritance in EF Core:
Define Your Base Class:
Start by defining a base class (abstract class or interface) representing the common properties and fields shared by all the child classes in your inheritance hierarchy. In our example, we are going to use the following abstract class.
using System.ComponentModel.DataAnnotations; namespace EFCoreCodeFirstDemo.Entities { public abstract class BaseEntity { [Key] public int Id { get; set; } public string CommonProperty { get; set; } } }
Define Your Derived Classes:
Create the derived classes that inherit from the abstract base class. Each derived class should have its own properties and fields, and they can be decorated with [Table] attributes to specify their table names. But in our example, we will use Fluent API configuration to provide the table names. So, create the following derived concrete classes.
namespace EFCoreCodeFirstDemo.Entities { public class DerivedEntity1 : BaseEntity { public string Property1 { get; set; } } public class DerivedEntity2 : BaseEntity { public string Property2 { get; set; } } }
Configure the Model:
In your DbContext class, use the modelBuilder to configure the TPC inheritance strategy. All the concrete types are mapped to individual tables in the TPC mapping pattern. For TPC mapping call the modelBuilder.Entity<BaseEntity>().UseTpcMappingStrategy() on each root entity type, as shown below.
using Microsoft.EntityFrameworkCore; namespace EFCoreCodeFirstDemo.Entities { public class EFCoreDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { //Configuring the Connection String optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertificate=True;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BaseEntity>().UseTpcMappingStrategy(); modelBuilder.Entity<DerivedEntity1>().ToTable("DerivedTable1"); modelBuilder.Entity<DerivedEntity2>().ToTable("DerivedTable2"); } public DbSet<BaseEntity> BaseEntites { get; set; } } }
Migrate and Apply Database Changes:
After defining your entities and configuring the model, you can use EF Core’s migration commands to create or update the database schema based on your model. So, open the Package Manager Console and Execute the add-migration and update-database commands as follows. You can give any name to your migration. Here, I am giving Mig3. The name that you are giving it should not be given earlier.
Now, you will have separate database tables for each derived type (DerivedTable1 and DerivedTable2). These tables will include the properties inherited from the base class (BaseEntity) and the properties specific to each derived class. Now, if you verify the database, then you will see the following:
Modify the Program class to check whether TPC Inheritance is working as expected. The following example code is self-explained, so please go through the comment lines for a better understanding. In the example below, the OfType method filters the entities retrieved from the DbSet based on their concrete type, either DerivedEntity1 or DerivedEntity2. The filtered entities are then loaded into memory using the ToList method and displayed in the console.
using EFCoreCodeFirstDemo.Entities; namespace EFCoreCodeFirstDemo { public class Program { static async Task Main(string[] args) { try { //Use Inheritance in our Code //Now, you can create and work with instances of the derived classes //and save them to the database using (var context = new EFCoreDbContext()) { var derivedEntityA = new DerivedEntity1 { Property1 = "SomeValue1", CommonProperty = "SomeCommonValue" }; context.BaseEntites.Add(derivedEntityA); var derivedEntityB = new DerivedEntity2 { Property2 = "SomeValue2", CommonProperty = "SomeCommonValue" }; context.BaseEntites.Add(derivedEntityB); context.SaveChanges(); Console.WriteLine("Entities are Added"); } //Query the Inheritance Hierarchy //You can query the inheritance hierarchy using LINQ queries using (var context = new EFCoreDbContext()) { var DerivedEntities1 = context.BaseEntites.OfType<DerivedEntity1>().ToList(); foreach (DerivedEntity1 derivedEntity1 in DerivedEntities1) { Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntity1.Id}, Property1: {derivedEntity1.Property1}, CommonProperty: {derivedEntity1.CommonProperty}"); } var DerivedEntities2 = context.BaseEntites.OfType<DerivedEntity2>().ToList(); foreach (DerivedEntity2 derivedEntity2 in DerivedEntities2) { Console.WriteLine($"\tDerivedEntityA: Id: {derivedEntity2.Id}, Property2: {derivedEntity2.Property2}, CommonProperty: {derivedEntity2.CommonProperty}"); } } Console.Read(); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); ; } } } }
Output:
Now, if you verify the database table, you will see the following data:
TPH vs. TPT vs. TPC in EF Core
In Entity Framework Core (EF Core), there are three common inheritance mapping strategies for modeling object-oriented inheritance hierarchies in a relational database: Table-Per-Hierarchy (TPH), Table-Per-Type (TPT), and Table-Per-Concrete-Type (TPC). Each strategy has its own advantages and disadvantages, and the choice of which one to use depends on your specific application requirements and design considerations. Here’s an overview of these three inheritance mapping strategies:
Table-Per-Hierarchy (TPH) in EF Core:
In TPH, the entire class hierarchy is mapped to a single database table. The table includes columns for all properties of all classes in the hierarchy. A discriminator column is used to identify which class each row corresponds to.
Advantages of Table-Per-Hierarchy (TPH in EF Core):
- Simple database schema. It results in fewer tables and less redundancy in the database schema since all types share the same table.
- Simple to implement and understand.
- Generally, it offers the best query performance since it does not require joining tables.
Disadvantages of Table-Per-Hierarchy (TPH in EF Core):
- The table can have many nullable columns if many properties are specific to only some derived classes.
- It is not suitable when the derived types have a significantly different set of properties.
Table-Per-Type (TPT) in EF Core:
Each class in the hierarchy in TPT is mapped to its own table. Properties defined in a base class are stored in a base table, and each derived class has its own table containing only the properties defined for that derived class. The tables are related through foreign keys.
Advantages of Table-Per-Type (TPT in EF Core):
- Clear separation of data: Each type has its own table, making the schema intuitive.
- There are no nullable columns since each table contains only the columns relevant to the respective type.
- Normalized database schema.
- Easy to maintain data integrity.
Disadvantages of Table-Per-Type (TPT in EF Core):
- Increased schema complexity due to multiple tables.
- Requires more complex joins, which can lead to worse performance for retrieval.
- Potentially more complex insert and update operations.
Table-Per-Concrete-Type (TPC) in EF Core:
In TPC, each concrete class in the hierarchy is mapped to its own table and includes columns for all class properties, including inherited ones. No tables are created for abstract classes or interfaces. There are no relations between the tables for each type.
Advantages of Table-Per-Concrete-Type (TPC in EF Core):
- Efficient for querying specific derived types without joins.
- It is suitable when derived types have significantly different sets of properties.
- There are no nullable columns since each table contains only the columns for the specific type.
Disadvantages of Table-Per-Concrete-Type (TPC in EF Core):
- Increased schema complexity due to multiple tables.
- This may lead to redundant data if the base class properties are stored in each table, increasing storage requirements.
- It is more complex to maintain data integrity across tables.
Choosing the Right Strategy
When choosing between these strategies, you should consider:
- Performance: TPH is generally the fastest, while TPT can suffer from performance issues due to the need for joins.
- Data Integrity: TPT and TPC can provide better data integrity and make it easier to enforce constraints.
- Complexity: TPH typically results in the simplest database schema and is easiest to work with in EF Core, while TPT and TPC can increase the complexity of the model.
Choosing the right inheritance mapping strategy depends on factors such as your data model, querying requirements, and your willingness to make performance considerations. TPH is often a good choice for hierarchies with shared properties and when you want a more compact schema. TPT and TPC in EF Core are better suited when you want a clear separation of data and better performance for querying specific derived types. Ultimately, the choice should align with your application needs and database design goals.
In the next article, I will discuss Transactions in Entity Framework Core with Examples. In this article, I try to explain Entity Framework Core (EF Core) Inheritance with Examples. I hope you enjoy this Entity Framework Core (EF Core) Inheritance article.
About the Author: Pranaya Rout
Pranaya Rout has published more than 3,000 articles in his 11-year career. Pranaya Rout has very good experience with Microsoft Technologies, Including C#, VB, ASP.NET MVC, ASP.NET Web API, EF, EF Core, ADO.NET, LINQ, SQL Server, MYSQL, Oracle, ASP.NET Core, Cloud Computing, Microservices, Design Patterns and still learning new technologies.