polymorphismentity-framework-coretph

EF Core - ThenInclude() on collection with polymorphic elements


I am trying to eager load properties from elements of a collection which itself is a property of another class. The elements of the collection are polymorphic, not sharing the properties I am trying to include, and tracked in the DB via TPH (Table-per-Hierachry). When I try to eager load them an exception is thrown stating that the base class does not contain the requested property.

I have an abstract base class Base and two derived classes DerivedA and DerivedB. Base has been configured for TPH like this

internal static void Configure(ModelBuilder builder)
{
    // Get the EntityTypeBuilder from the given ModelBuilder
    EntityTypeBuilder<Base> entityBuilder = builder.Entity<Base>();

    // Configure TPH
    entityBuilder.ToTable("Bases").HasDiscriminator<int>("DerivedType")
                 .HasValue<DerivedA>(0)
                 .HasValue<DerivedB>(1);
}

Furthermore I have a class ToBeLoaded with a property public ICollection<Base> Bases {get; set; } which does contain both DerivedA and DerivedB. I am expecting EF Core to be able to handle this. Am I wrong with that?

The EF Core Docs say I can use as or a direct cast within ThenInclude() like I do in my extension method.

public static IQueryable<ToBeLoaded> LoadRelated(this DbSet<ToBeLoaded> toBeLoadedSet)
{
    return toBeLoadedSet.Include(tbl => tbl.Bases)
                               .ThenInclude(b => (b as DerivedA).PropA)
                               .Include(tbl => tbl.Bases)
                               .ThenInclude(b => (b as DerviedB).PropB);
}

When then calling context.ToBeLoadedSet.LoadRelated.ToList(); the following exception is thrown

System.InvalidOperationException: 'The property 'PropA' is not a navigation property of entity type 'Base'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names.'

I already tried to use the other ways to achieve this suggested in the docs, namely direct cast and the Include(string) method. I am aware that this is somewhat different from the example in the docs, but that is the closest thing to my situation I could find.

Is this even theoretically possible using the Include interface or should I just try to use RawSQL?


Solution

  • So after writing the whole question up I tried one more thing. Just including Bases like toBeLoadedSet.Include(tbl => tbl.Bases). For some reason I thought I tried this before and then PropA and ProbB where null, but just wanted to be sure.

    Since it's there now anyways and it might prevent someone else from wasting their time, I will just post it. Thanks for being my rubber duck.

    To be clear, the LoadRelated method looks like this now

    public static IQueryable<ToBeLoaded> LoadRelated(this DbSet<ToBeLoaded> toBeLoadedSet)
    {
        return toBeLoadedSet.Include(tbl => tbl.Bases);
    }