Search code examples
c#.netentity-frameworkctp4

CTP 4 doesn't consider base class properties


In Below sample, CTP consider only User class properties but i expect base class properties included in Users table.

Is there a way to tell CTP include base class properties ? Or what can be alternative solution ?

class PersonDBContext : DbContext
{
    public DbSet<User> UserSet { get; set; }
}
class Person
{
    public string Firstname { get; set; }
    public string SurName { get; set; }
}
class User : Person
{
    public int UserId { get; set; }
    public string CreatedOn { get; set; }
}

Solution

  • That's because you declared DbSet<User> in your DbContext instead of DbSet<Person>. EF code first has a concept of reachability when it comes to inheritance which basically means if you put a set of the base class in the context, it will go down and find and include all of its sub classes.

    There is also one more problem here: you haven't specified a primary key for your Person class and EF could not infer one based on the Code First conventions as well. By default, EF maps inheritance in the model to TPH (Type Per Hierarchy) in the database and a sub class inherits the key from its base class which has been put in the sub class (i.e. UserId) in your model.

    To fix this, we first change the DbSet on DbContext to be of type Person and then we need to move up UserId to the Person base class and make it a Key as follows:

    public class Person 
    {
        [Key]
        public int UserId { get; set; }
    
        public string Firstname { get; set; }
        public string SurName { get; set; }
    }
    
    public class User : Person 
    {        
        public string CreatedOn { get; set; }
    }
    
    public class PersonDBContext : DbContext 
    {
        public DbSet<Person> UserSet { get; set; }        
    }
    

    This model will result in the following DB schema which has all the columns from both classes (note pluralization support):

    alt text

    How to change TPH to TPT (Table Per Hierarchy) in Code First:

    Like I said, by convention, EF code first will use TPH to map inheritance to the database and to change it to TPT we need to override the conventions by using the Fluent API:

    public class StackOverflowContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Person>().MapHierarchy(p => new
            {
                p.UserId,
                p.Firstname,
                p.SurName,
            })
            .ToTable("Person");
    
            modelBuilder.Entity<User>().MapHierarchy(u => new
            {
                u.UserId,
                u.CreatedOn
            })
            .ToTable("User");
        }
    }
    

    This model will result in the following DB schema:

    alt text

    How to change TPH to TPC (Table Per Concrete Type) in Code First:

    In Table per Concrete Class there is a table for each class, and each of those tables has a column for every property of that type.

    The code for this is shown below, note that I switch off identity on the primary key property because there is no foreign key between the two tables and we need to take care of providing unique keys.

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>().Property(p => p.UserId)
        .StoreGeneratedPattern = System.Data.Metadata.Edm.StoreGeneratedPattern.None;
    
        modelBuilder.Entity<Person>().MapSingleType(p => new
        {
            p.UserId,
            p.Firstname,
            p.SurName,
        })
        .ToTable("Person");
    
        modelBuilder.Entity<User>().MapSingleType(u => new
        {
            u.UserId,
            u.CreatedOn,
            u.Firstname,
            u.SurName,
        })
        .ToTable("User");
    }
    

    This model will result in the following DB schema: alt text