Hello, studying the materials on the Entity Framework read about the possibilities and different approaches to the implementation of inheritance in this framework. And I was certainly interested in this question:

What is the difference between their use and implementation during development?

What points I will cover when forming the answer:

  1. Theory - here I will tell you what approaches there are and how they differ in terms of implementation

  2. Practice - here I will show the very implementation of the code of the class model + show how the table / database table will look like with different approaches

  3. A characteristic comparison - in the final section we will find out which approach, in which category, and why this particular approach is given preference.

The value of this article : while studying, in fact, these same approaches, I immediately got the idea to create a manual where everyone can clearly see the difference in their use and, based on characteristics and practical examples, see and decide for themselves which approach will be optimal

    1 answer 1

    So let's start perhaps.

    Theoretical part :

    In total, the Entity Framework has only 3 approaches:

    1. TPH (Table Per Hierarchy - Table per class hierarchy)
    2. TPT (Table Per Type - Table to Type)
    3. TPC (Table Per Class (Concrete Type) - Table for each individual type / class)

    1) TPH (Table Per Hierarchy - Table per class hierarchy) - When using this approach - one table is used for one class hierarchy.

    The data of the base and derived classes are saved in one table, and a special column is created to distinguish them.

    2) TPT (Table Per Type) - this approach assumes that in the general table only those properties that are common to all heir classes, that is, that are defined in the base class, are saved.

    And those properties that relate only to the derived class are stored in a separate table.

    3) TPC (Table Per Class (Concrete Type) - Table for each individual type / class) - involves the creation of a separate table for each model. Columns in each table are created for all properties, including inherited.

    Here we are probably going to finish with the theoretical part - let's move on to practice :

    The practical part:

    I consider that it is necessary to find out one detail at once:

    The essence and purpose of these approaches is the same - to display the dependence of the heir class on the parent class, and the difference is that I will show which approach will be more optimal from different characteristic sides (and of course, the differences in their practical implementation). Thanks for attention!

    1) TPH approach:

    So, we have a basic mini-hierarchy of two classes:

      public class Phone { public int Id { get; set; } public string Name { get; set; } public string Company { get; set; } public int Price { get; set; } } public class Smartphone : Phone { public string OS { get; set; } } class MobileContext : DbContext { public MobileContext() : base("DefaultConnection") { } public DbSet<Phone> Phones { get; set; } public DbSet<Smartphone> Smarts { get; set; } } 

    Here the class Smartphone inherited from Phone , defining one property in addition to the inherited ones.

    And during the work such table will be created:

    Table view when approaching TPH

    In addition to all the properties of the Phone and Smartphone classes, another column appears here - Discriminator . It is of type nvarchar and has a length of 128 characters. This column will determine whether the row is of the type Phone or Smartphone .

    Work in the program:

     using(MobileContext db = new MobileContext()) { db.Phones.Add(new Phone {Name = "Samsung Galaxy S5", Company = "Samsung", Price = 14000 }); db.Phones.Add(new Phone {Name = "Nokia Lumia 630", Company = "Nokia", Price = 8000 }); Smartphone s1 = new Smartphone { Name = "iPhone 6", Company = "Apple", Price = 32000, OS = "iOS" }; db.Smarts.Add(s1); db.SaveChanges(); foreach (Phone p in db.Phones) Console.WriteLine("{0} ({1}) - {2}", p.Name, p.Company, p.Price); Console.WriteLine(); foreach (Smartphone p in db.Smarts) Console.WriteLine("{0} ({1}, {2}) - {3}", p.Name, p.Company, p.Price, p.OS); } 

    I ask you to pay attention to one detail: when outputting data from Phones Smartphone instance will also be output along with the other instances, since here SmartPhone is the Phone object.

    2) TPT approach:

    To apply the approach, take the class system from the previous example and add the [Table] attribute to the Smartphone class:

     public class Phone { public int Id { get; set; } public string Name { get; set; } public string Company { get; set; } public int Price { get; set; } } [Table("Smartphones")] public class Smartphone : Phone { public string OS { get; set; } } class MobileContext : DbContext { public MobileContext() : base("DefaultConnection") { } public DbSet<Phone> Phones { get; set; } public DbSet<Smartphone> Smarts { get; set; } } 

    Everything else remains the same as with the approach of TPH . But now the database will contain the following tables:

    TPT approach

    The table for smartphones contains only one OS field, as well as the Id key for communication with the Phones table.

    The use of models will be similar to the TPH approach (point above) :

     using(MobileContext db = new MobileContext()) { db.Phones.Add(new Phone {Name = "Samsung Galaxy S5", Company = "Samsung", Price = 14000 }); db.Phones.Add(new Phone {Name = "Nokia Lumia 630", Company = "Nokia", Price = 8000 }); Smartphone s1 = new Smartphone { Name = "iPhone 6", Company = "Apple", Price = 32000, OS = "iOS" }; db.Smarts.Add(s1); db.SaveChanges(); foreach (Phone p in db.Phones) Console.WriteLine("{0} ({1}) - {2}", p.Name, p.Company, p.Price); Console.WriteLine(); foreach (Smartphone p in db.Smarts) Console.WriteLine("{0} ({1}, {2}) - {3}", p.Name, p.Company, p.Price, p.OS); } 

    3) TPC Approach:

    To apply the approach, we change the model declarations and context as follows:

     public class Phone { [Key, DatabaseGenerated (DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } public string Name { get; set; } public string Company { get; set; } public int Price { get; set; } } public class Smartphone : Phone { public string OS { get; set; } } class MobileContext : DbContext { public MobileContext() : base("DefaultConnection") { } public DbSet<Phone> Phones { get; set; } public DbSet<Smartphone> Smarts { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Phone>() .Map(m => { m.MapInheritedProperties(); m.ToTable("Phones"); }); modelBuilder.Entity<Smartphone>().Map(m => { m.MapInheritedProperties(); m.ToTable("Smarts"); }); } } 

    First , note that the Phone class doesn’t use Guid as the key type. This will help us to avoid some problems with the keys. Although it would also be possible to use int with a manual setting of Id when creating the object.

    Secondly , when configuring the mapping between models and tables, each model calls the MapInheritedProperties() method, which indicates the Entity Framework , that inherited properties should be included in the table for this model, and not only those that are defined directly in this model.

    When generating the database, we will have two tables with a full set of columns:

    TPC approach

    Application models:

     using(MobileContext db = new MobileContext()) { db.Phones.Add(new Phone {Name = "Samsung Galaxy S5", Company = "Samsung", Price = 14000 }); db.Phones.Add(new Phone {Name = "Nokia Lumia 630", Company = "Nokia", Price = 8000 }); Smartphone s1 = new Smartphone { Name = "iPhone 6", Company = "Apple", Price = 32000, OS = "iOS" }; db.Smarts.Add(s1); db.SaveChanges(); foreach (Phone p in db.Phones) Console.WriteLine("{0} ({1}) - {2}", p.Name, p.Company, p.Price); Console.WriteLine(); foreach (Smartphone p in db.Smarts) Console.WriteLine("{0} ({1}, {2}) - {3}", p.Name, p.Company, p.Price, p.OS); } 

    Although the Smartphone object is not related to the Phones table, it will also be in the db.Phones set when extracting data, because inheritance will still work.

    Go to the third part - Characteristic comparison:

    Here it’s already really interesting: Depending on your requirements and the requirements of the customer, there is simply no 'better' solution ... But:

    The Entity Framework engine supports the TPC approach, but for its adequate work we need to OnModelCreating() method in order for the framework to understand that we connect two classes to each other with hereditary connections using this approach in the application.

    Does this mean that in most cases we will have to use the 2 first approaches - TPH and TPT , so as not to waste time on tricks with TPC and the relative speed of the application when receiving requests?

    Let's find out!

    So, here we use the format "criterion - conditional winner - why" :

    1. Speed ​​of execution (work) - TPH -> Table for one class hierarchy in general has the best speed, if only because there is no need to make JOIN queries since all the data are in one table. Such a decision becomes even more obvious when our hereditary hierarchy becomes “wider” and “deeper” .
    2. Flexibility - TPT -> Table for type is more flexible because it solves the problem of editing and updating the columns of the child-table without changing the parent table.
    3. Aesthetics - TPT -> this is a purely subjective opinion, but as for me, TPT looks for a less object-oriented approach.
    4. Memory use - TPT -> if your inheritance hierarchy has very many different types, then using TPT will allow you to use data that has a lot of empty fields, especially if the database structure solves the problem of a set of empty fields, then this problem is unlikely to affect query performance.

    As we can see, if you or your customer know which criteria to focus on, then you can make an obvious choice based on the information above.

    The only caveat is that in 90% of cases, the rate goes to Performance , so in most cases using TPH (Table per hierarchy) to optimize query performance will be better

    I hope this article was able to bring some clarity to the question - if you have any wishes, the comments are always open to you - Thank you for your attention, comrades!

    Of course, links to sources: practical examples and the use of these approaches in applications + distinctive characteristics of each of the approaches