Search code examples
c#entity-framework-6table-per-class

TPC in Entity Framework v6


I'm trying to do a pretty simple thing in Entity Framework.

I have a product that has zero or more parameters and these parameters will be mapped to their own tables. However, I'm unable to get this to work. I've been trying to get the mappings right and then use the migrations to see what the database is supposed to look like. I know that this is very simple in NHibernate, but I'm forced against my will to use Entity Framework v6.

Background

These are my entities:

namespace Entities
{
    public class EntityState
    {
        public int Id { get; set; }
    }

    public class ProductState : EntityState
    {
        public virtual ICollection<ProductParameterState> Parameters { get; set; }
    }

    public abstract class ProductParameterState : EntityState
    {
    }

    public class ColorParameterState : ProductParameterState
    {
        public virtual string Color { get; set; }
    }

    public class SizeParameterState : ProductParameterState
    {
        public virtual int Size { get; set; }
    }
}

I would like to store this in the following schema:

ERD

How to do this?

My attempts

Table-per-class

I tried mapping using TPC:

namespace Mappings
{
    public class ProductMap : EntityTypeConfiguration<ProductState>
    {
        public ProductMap()
        {
            HasKey(x => x.Id);
            Property(x => x.Name);
            HasMany(x => x.Parameters);
        }
    }

    public class ColorParameterMap : EntityTypeConfiguration<ColorParameterState>
    {
        public ColorParameterMap()
        {
            HasKey(x => x.Id);
            Property(x => x.Color);
            Map(x =>
            {
                x.ToTable("ColorParameters");
                x.MapInheritedProperties();
            });
        }
    }

    public class SizeParameterMap : EntityTypeConfiguration<SizeParameterState>
    {
        public SizeParameterMap()
        {
            HasKey(x => x.Id);
            Property(x => x.Size);
            Map(x =>
            {
                x.ToTable("SizeParameters");
                x.MapInheritedProperties();
            });
        }
    }
}

But this gives the error The association 'ProductState_Parameters' between entity types 'ProductState' and 'ProductParameterState' is invalid. In a TPC hierarchy independent associations are only allowed on the most derived types..

Don't use an inheritence strategy

So I tried to remove the MapInheritedProperties, but then it wants to create an additional, and unwanted, table:

CreateTable(
    "dbo.ProductParameterStates",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            ProductState_Id = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.ProductStates", t => t.ProductState_Id)
    .Index(t => t.ProductState_Id);

I don't want this. I am able to get rid of this one by removing the Parameters property in Product, but then I'm not able to use the Parameters of a Product.

Am I asking for too much or is it possible?


Solution

  • You can use TPC, but the relationship must be bidirectional with explicit FK defined (which I guess is the opposite of "independent association" mentioned in the error message).

    Add inverse navigation property and FK property to your base entity:

    public abstract class ProductParameterState : EntityState
    {
        public int ProductId { get; set; }
        public ProductState Product { get; set; }
    }
    

    and use the same entity configurations as in your first attempt, except for the ProductMap where you either remove the following

    HasMany(x => x.Parameters);
    

    or change it to

    HasMany(e => e.Parameters)
        .WithRequired(e => e.Product)
        .HasForeignKey(e => e.ProductId);