Type-specific Table (TPC) Inheritance in Entity Framework 6 (EF6)

In an effort to avoid using a table per hierarchy (TPH), I looked at examples of how best to implement table-based inheritance for each class (TPC) in my database model. I came across official documentation and this article .

Below are some class layouts with some simple inheritance.

public class BaseEntity { public BaseEntity() { ModifiedDateTime = DateTime.Now; } [Key] [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public DateTime ModifiedDateTime { get; set; } } public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } } public class Business : BaseEntity { public string Name { get; set; } public string Location { get; set; } } 

DbModelBuilder configurations used in the examples in both articles.

 modelBuilder.Entity<BaseEntity>() .Property(c => c.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); modelBuilder.Entity<Person>().Map(m => { m.MapInheritedProperties(); m.ToTable("Person"); }); modelBuilder.Entity<Business>().Map(m => { m.MapInheritedProperties(); m.ToTable("Business"); }); 

The application starts successfully, but when I return to the database, I find three (3) tables instead of the two (2) that I expected to find. After a little testing, the "BaseEntity" table will appear, but it is never used. Everything seems to work just fine, except for an empty blank axis.

I deal with DbModelBuilder configurations, eventually removing the "BaseEntity" configurations that provide the expected result; Two (2) tables, each of which has the correct properties and works correctly.

I do one last test, destroy all DbModelBuilder configurations, include only two properties (2) of DbSet for "Person" and "Business" and test again.

 public DbSet<Person> People { get; set; } public DbSet<Business> Businesses { get; set; } 

To my surprise, the project is being built, goes to the database, creates only two tables with all the properties of the class, including those inherited from the BaseEntity class. I can do CRUD operations without problems.

After running many tests, I can not find any problems with the final test, and I could not reproduce the duplicate key error, which both articles warned about.

The changes to the database were completed successfully, but an error occurred while updating the context of the object. ObjectContext may be in an inconsistent state. Internal exception message: AcceptChanges cannot continue because the object key values ​​conflict with another object in the ObjectStateManager. Make sure the key values ​​are unique before calling AcceptChanges.

  • I'm curious why the examples use the MapInheritedProperties property; is this an obsolete method?
  • Why do both examples talk about enabling configuration properties for "BaseEntity", but including either the DbSet property, or any DbModelBuilder configurations for the "BaseEntity" class, force you to create an unused table.
  • In relation to the unique key error that the articles warned about; I cannot reproduce the error, and I have tested the primary key many times, both the int generated by the database and the directives generated by the database. Is the information about this error also outdated, or is there a test that I can run to produce the indicated error?
+8
inheritance c # sql-server entity-framework
source share
2 answers

Just to make it all easier, I moved the code needed to get TablePerConcrete to open the source code. Its purpose is to enable functions that are usually only available on the Fluent Interface (where you need to scatter a lot of code into your OnModelCreating method of the Db class) in order to move on to attribute-based functions.

This allows you to do things like this:

 [TablePerConcrete] public class MySubclassTable : MyParentClassEntity 

Enforcing a TPC regardless of what the EF may decide to infer from the parent class / subclass relationship.

One interesting issue here is that sometimes EF will corrupt the inherited Id property by setting it to fill with an explicit value, rather than creating a database. You can make sure that this does not happen if the interface of the parent class implements IId (which simply says: “This property has the Id property”) and then subclasses [ForcePKId] .

 public class MyParentClassEntity : IId { public int Id { get; set; } . . . [TablePerConcrete] [ForcePKId] public class MySubclassTable : MyParentClassEntity { // No need for PK/Id property here, it was inherited and will work as // you intended. 

Removing code that handles all this for you is pretty simple - just add a couple of lines to the Db class:

 public class Db : DbContext { . . . protected override void OnModelCreating(DbModelBuilder modelBuilder) { var modelsProject = Assembly.GetExecutingAssembly(); B9DbExtender.New().Extend(modelBuilder, modelsProject); 

You can access it in one of two ways:

+2
source share

I use matching classes, but don't think so. I solve it as follows:

 public class PersonMap : EntityTypeConfiguration<Person> { public PersonMap() { Map(m => { m.ToTable("Person"); m.MapInheritedProperties(); }); HasKey(p => p.Id); Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } } 

Remember - the base class must be abstract.

+1
source share

All Articles