Search code examples
c#entity-framework-coretable-per-hierarchy

Entity Framework core 3.1.1 Multilevel Inheritance


An exception is thrown while running add-migration (EF core 3.1.1):

CLR property 'DiscriminatorLevel2Id' cannot be added to entity type 'CustomerBase' because it is declared on the CLR type 'InternalCustomer'

The following Image shows the needed hierarchy (briefly):

Class Diagram

Mapping looks like:

// Discriminator (Level 1)
modelBuilder.Entity<CustomerBase>()              
            .HasDiscriminator(b => b.CustomerTypeId)
            .HasValue<InternalCustomer>((int)CustomerType.Internal)
            .HasValue<ExternalCustomer>((int)CustomerType.External);

// Discriminator (Level 2)
modelBuilder.Entity<InternalCustomer>()
         .HasDiscriminator(b => b.DiscriminatorLevel2Id)
         .HasValue<VIPCustomer>((int)DiscriminatorLevel2.VIP)
         .HasValue<RegularCustomer>((int)DiscriminatorLevel2.Regular);

Is "Multilevel Inheritance TPH" supported on Entity Framework Core 3.1.1?


Solution

  • It's possible, but with single shared discriminator at the root abstract level containing values for all possible creatable (non abstract) direct or indirect derived entities.

    Applying to your sample requires removing the DiscriminatorLevel2 property (column), removing Internal from CustomerType enum (assuming InternalCustomer is abstract) and merging Regular and VIP into it, e.g. something like this:

    Model:

    public abstract class CustomerBase
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int CustomerTypeId { get; set; }
    }
    
    public abstract class InternalCustomer : CustomerBase
    {
        public decimal Points { get; set; }
    }
    
    public class RegularCustomer : InternalCustomer
    {
        public int PartnerId { get; set; }
    }
    
    public class VIPCustomer : InternalCustomer
    {
        public string CardNo { get; set; }
    }
    
    public class ExternalCustomer : CustomerBase
    {
    }
    
    public enum CustomerType { External, Regular, VIP }
    

    Configuration:

    modelBuilder.Entity<CustomerBase>()
        .HasDiscriminator(b => b.CustomerTypeId)
        .HasValue<ExternalCustomer>((int)CustomerType.External)
        .HasValue<VIPCustomer>((int)CustomerType.VIP)
        .HasValue<RegularCustomer>((int)CustomerType.Regular);
    
    modelBuilder.Entity<InternalCustomer>();
    

    When you want to query InternalCustomer derived entities, you could use db.Set<InternalCustomer>() or db.Set<CustomerBase>().OfType<InternalCustomer>() and EF Core will apply filter similar to t.CustomerTypeId IN (1,2), i.e. the IN clause will contain list of discriminator values for all final entities derived from InternalCustomer.