How to Automatically Map TPH Derived Classes in EF Core?
Asked Answered
S

1

5

By default, EF6 would map a base abstract class and it's derived classes for Table Per Hierarchy (TPH).

EF Core no longer follows this logic and requires derived classes to be opted in. The documentation states:

By convention, types that are exposed in DbSet properties on your context are included in the model as entities. Entity types that are specified in the OnModelCreating method are also included, as are any types that are found by recursively exploring the navigation properties of other discovered entity types.

Using this approach is not too hard to follow if you have a few sub-types as you can just add them as DbSets or add a HasDiscriminator().HasValue() for each sub-type with mapping as below:

builder.HasDiscriminator()
    .HasValue<CommaSymbolRule>("CommaSymbolRule")
    .HasValue<DashSymbolRule>("DashSymbolRule")
    .HasValue<IsNumericSymbolRule>("IsNumericSymbolRule")
    .HasValue<IsPunctuationSymbolRule>("IsPunctuationSymbolRule")
    .HasValue<PeriodSymbolRule>("PeriodSymbolRule")

In some circumstances this is a sub-optimal as you may have many derived classes. In my case, I have a rules engine and do not want to have map each rule individually.

Is there a way to automatically map the sub-types of a base class in an EF Core Table Per Hierarchy scenario without having to manually add them?

Shulock answered 18/1, 2021 at 16:58 Comment(0)
S
9

I thought there may be a way to do this in EF Core, but found that there was not one.

I discussed with the EF team why automatic opt-in is no longer the default and they gave concerns about the stability of "assembly scanning solutions" which are probably very valid. They did not seem too interested in adding a new feature along those lines at this moment.

Here is what I came up with instead. It puts the assembly scanning in the mapping code but seems to work.

First, I created an extension method to get the derived classes (which optionally can ignore particular types by name):

public static Type[] GetDerivedClasses(this Type type, string[] ignoreTypeNames = null) 
{
    ignoreTypeNames = ignoreTypeNames ?? new string[0];

    return Assembly.GetAssembly(type)
                    .GetTypes()
                    .Where
                    (
                        t => t.IsSubclassOf(type) &&
                        (!ignoreTypeNames?.Any(t.Name.Contains) ?? false)
                    )
                    .OrderBy(o => o.Name)
                    .ToArray();
}

Then use code similar to this for the base class in the EF Core mapping, switching out your type (ie: "SymbolRule" in this code):

public void Configure(EntityTypeBuilder<SymbolRule> builder)
{
    builder.ToTable("SymbolRule");       // my example table
    builder.HasKey(t => t.SymbolRuleId); // my example key


    foreach (var type in typeof(SymbolRule).GetDerivedClasses())
    {
        builder.HasDiscriminator()
               .HasValue(type, type.Name);
    }
}

The foreach gets the derived classes from the base class and loops through them and adds a discriminator type for each.

Shulock answered 18/1, 2021 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.