Hugo
Hugo

Reputation: 119

ADO EF Code First Generic Intermediate Class Inheritance mapping

I've got the following requirement that works well in the OO space but I can't seem to get it to map back to the DB using ADO EF code first.

I have numrous products each will have different aspects (attributes but not in the sense of code attributes). For instance ring would have aspects such as mineral type = gold etc whilst a diamond would have an aspec of clarity = VVSI1.

As you can see the products very greatly in thier composition and I want a dynamic way of growing my system.

As such I've created a product class:

public class Product
{
    public int id { get; set; }
    public string Name { get; set; }
    private List<ProductAspect> aspects = new List<ProductAspect>();
    public List<ProductAspect> Aspects { get { return aspects; } set { aspects = value; } }
}

It has a list of ProductAspect which is the base class for all aspects moving forward:

public class ProductAspect 
{
    public int id { get; set; }
    public string AspectName { get; set; }
}

I then inherit from the ProductAspect using a generic which alows me to be specific (strongly typed) about my Aspect Value:

public abstract class ProductAspect<T> : ProductAspect
{
    public T AspectValue { get; set; }
}

I then create some Aspects that will allow me to decorate my product:

public class StringAspect : ProductAspect<string> { };
public class DecimalAspect : ProductAspect<decimal> { };
public class ImageAspect : ProductAspect<byte[]> { };

I then give the DbContext a try and have tried both TPH and TPC inheritance mappings.

Neither seem to work. The DB model that get's generated doesn't create a foriegn key to the StringAspect or DecimalAspect tables from the Aspect Table.

public class IxamDataContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<ProductAspect> Aspects { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        AspectMapping(modelBuilder);
    }

    private void AspectMapping(DbModelBuilder mb)
    {
        //TPH
        //mb.Entity<ProductAspect>()
        //    .Map<StringAspect>(m => m.Requires("type").HasValue("sa"))
        //    .Map<DecimalAspect>(m => m.Requires("type").HasValue("da"));

        //TPC
        //mb.Entity<StringAspect>().ToTable("StringAspect");
        //mb.Entity<DecimalAspect>().ToTable("DecimalAspect");
    }
}

Resulting in the following exception for this Seeding code:

        Product p = new Product();
        p.Name = "Diamond";
        p.Aspects.Add(new StringAspect() { AspectName = "History", AspectValue = "Old and long" });
        p.Aspects.Add(new DecimalAspect() { AspectName = "Weight", AspectValue= 96.5M });


        context.Products.Add(p);
        context.SaveChanges();

Excpetion:

EntityType 'StringAspect' does not exist in the EntitySet 'IxamDataContext.Aspects'. Parameter name: entity

Any ideas from the EF code first pros out there?

Upvotes: 6

Views: 2406

Answers (2)

Yair Nevet
Yair Nevet

Reputation: 13003

Maybe it's to late, but I would offer you using ComplexType attribute it will allows you to extend your types as you wish.

Upvotes: 0

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364249

Entity framework doesn't support intermediate non mapped types in inheritance hierarchy. It means that you can't have this inheritance: A (mapped) -> B (not mapped) -> C (mapped). EF also doesn't support mapping generic types. It means that you must remove your generic intermediate class from the hierarchy and move AspectValue to derived types with correct type.

Upvotes: 4

Related Questions