jairhumberto
jairhumberto

Reputation: 614

Entity Framework TPH changing discriminator column in an abstract base class and empty inherited class scenario

I have a table:

Groups
 - Id
 - Name
 - Type

And this table is a group of Products, Clients or Suppliers. To group products I need to use "P" in the Type column, to Group clients and suppliers, I need to use "C" and "S" respectivelly.

I wonder if I could use TPH here. Like creating an abstract class Group with all fields but Type, then creating empty subclasses ProductGroup, ClientGroup and SupplierGroup, then configuring the Type column to be the discriminator with the values "P", "C" and "S" to map to their respective classes.

I try to do this, but seems that EF don't guess my intention correctly when the Group class is abstract or when the inherited classes are empty.

My classes are like follow:

abstract class Group {
    public int Id { get; set; }
    public string Name { get; set; }
}

class ProductGroup : Group
{}

// I did not create the other two classes yet.

My configuration in DbContext is like follow:

public class DataContext : DbContext
{
    public DbSet<ProductGroup> ProductGroups { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new GroupConfiguration());
    }
}

class GroupConfiguration : EntityTypeConfiguration<Group>
{
    public GroupConfiguration()
    {
        Map<ProductGroup>(m =>
        {
            m.Requires("Type").HasValue("P");
        });
    }
}

I think it should work, but using the code above, the datacontext.ProductGroups will return all groups, not only the groups of products.

I tested and discover that if my Group class is concrete, the code will work as I expect, the groups in datacontext.ProductGroups will be only the groups of products.

I also tested and discover that if I keep my Group class abstract but move the field "Name" for the ProductGroup class, the code will also work as I expect.

I wonder if it is a bug, or am I in the wrong path.

I came up with this idea, because my boss wants an endpoint "/productgroups" in our web api, and I found that would be interesting to have an specific product group class to be used in the system, instead of using a generic group and have to filter .Where(e => e.Type == "P") everywhere. I think the latter aproach is less clear, when working with objects being passed around.

Anybody knows how to solve this? Will I really have to turn my Group class concrete? I would not like it, the Group class seems to be needed to be abstract, since in practice I only have groups with the Type field populated.

Upvotes: 1

Views: 2196

Answers (3)

jairhumberto
jairhumberto

Reputation: 614

I discovered the problem.

I was using the type Group somewhere else in the code. When I removed all the references to the Group type, it worked!

Thank you for all the answers though

Upvotes: 0

Ivan Stoev
Ivan Stoev

Reputation: 205829

What are you missing is that the EF inheritance is based on a single polymorphic DbSet, while the different strategies (TPH, TPT and TPC) control the physical storage model inside the database.

With that being said, you need the following in your DbContext (remove the ProductGroups that you put so far):

public DbSet<Group> Groups { get; set; }

All the CRUD operations should go through that DbSet. In case you need a specific descendants, you are supposed to use OfType method. Please note that it might introduce more issues than using a single class with explicit Type property since EF does not provide you any access to the discriminator column, so think carefully before deciding to use it just for that purpose.

Upvotes: 2

Mark Wagoner
Mark Wagoner

Reputation: 1767

Not sure if this is the problem but I place my mappings in the OnModelCreating method of the DbContext:

// Define DisplayLocation subclasses using discriminator column

modelBuilder.Entity<AbstractDisplayLocation>()
            .Map<VirtualDisplayLocation>(m => m.Requires("Virtual").HasValue(true))
            .Map<PdbLocation>(m => m.Requires("Virtual").HasValue(false));

Upvotes: 1

Related Questions