3

I'm trying to set up a TPC inheritance using Code First. I have a three level heirarchy. Abstract class A, concrete class B inherits from A and a class C inherits from B. Class A properties: ID, CreatedBy and CreatedOn. Class B properties: FirstName, LastName, BirthDate Class C properties: Appointmentdate, Status

I want tables for class B and class C to be created in the database with identity generated by the database.

I have following code in my context class:

public DbSet<B> BList { get; set; }
public DbSet<C> CList { get; set; }

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

        modelBuilder.Entity<B>().ToTable("BClass");
        modelBuilder.Entity<C>().ToTable("CClass");
    }

I have a contextinitializer class which override Seed method to populate some test data in database.

protected override void Seed(TestContext context)
    {
        TestDataClass testData = new TestDataClass();

        List<B> bList= testData.GetBList();

        foreach (B objB in bList)
        {
            context.BList.Add(objB);
        }

        List<C> cList = testData.GetCList();
        foreach (C objC in cList)
        {
            context.CList.Add(objC);
        }

        context.SaveChanges();
    }

This creates the tables BClass and CClass in the database but the problem is if Iam inserting 10 rows in BClass and corresponding 10 rows in CClass, the entity framework inserts 20 rows in BClass with 10 rows with actual values and 10 rows with Nulls. CClass gives the expected results with 10 rows. The second problem is I don't get the results when i query for the data from CClass. I error message says:

The EntitySet 'CClass' is not defined in the EntityContainer 'TestContext'. Near simple identifier, line 1, column 12.

I am using code from Huy Nguyen's post for Entity Framework 4 POCO, Repository and Specification Pattern [Upgraded to EF 4.1] from http://huyrua.wordpress.com/2011/04/13/entity-framework-4-poco-repository-and-specification-pattern-upgraded-to-ef-4-1/ and my code in generic repository looks like

public IList<TEntity> FindAll<TEntity>() where TEntity : class
    {
        DbContext.Set<TEntity>();
        var entityName = GetEntityName<TEntity>();
        return ((IObjectContextAdapter)DbContext).ObjectContext.CreateQuery<TEntity>(entityName).ToList<TEntity>();
    }

        private string GetEntityName<TEntity>() where TEntity : class
    {
        DbContext.Set<TEntity>();
        return string.Format("{0}.{1}", ((IObjectContextAdapter)DbContext).ObjectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));
    }

I tried changing my table mappings but it didn't help. I don't know what am i missing here and not sure what to do. I am in urgent need of help.

Thanks in advance.


This is how my actual classes looks like:

public abstract class EntityBase
{
    [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
    public virtual long Id { get; set; }
    public virtual string CreatedBy { get; set; }
    public virtual DateTime? CreatedOn { get; set; }
    public virtual string LastModifiedBy { get; set; }
    public virtual DateTime? LastModifiedOn { get; set; }

}


public class Person: EntityBase
{
    public virtual string FirstName { get; set; }
public virtual string MiddleName { get; set; }
    public virtual string LastName { get; set; }
    public virtual DateTime? BirthDate { get; set; }


    [NotMapped]
    public virtual string FullName
    {
        get
        {
            return String.Format("{0}, {1} {2}", LastName, FirstName, MiddleName);
        }
    }
}

public partial class Employee : Person
{
[Required]
    public string EmployeeNumber { get; set; }
[Required]
public string department { get; set; }

    public  DateTime? JoiningDate { get; set; }
    public  DateTime? PromotionDate { get; set; }
 }

and my context class now has:

public DbSet<EntityBase> People { get; set; }

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

        modelBuilder.Entity<Person>().ToTable("Person");
        modelBuilder.Entity<Employee>().Map(m => m.MapInheritedProperties()).ToTable("Employee"); 
}
Amit
  • 501
  • 1
  • 9
  • 16

1 Answers1

7

Your mapping specifies TPT not TPC. You must use:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<B>().ToTable("BClass"); 
    modelBuilder.Entity<C>().Map(m => m.MapInheritedProperties()).ToTable("CClass");
}

I want tables for class B and class C to be created in the database with identity generated by the database.

EF will not do this for you. This will require custom modification of generated code because once using inheritance entities must have unique Id among all instances in whole inheritance hierarchy = id must be unique between both B and C tables. This can be easily done by specifying identity with seed 0 and increment 2 for B and seed 1 and increment 2 for C but you must modify this each time you add a new child entity type.

Your repository is terrible. Why are you converting back to ObjectContext when you are using DbContext? Read something about using and querying DbContext and start without repositories. Also ObjectContext API doesn't allow using derived types in query. You must query for top level mapped entity type and use OfType extension method to get only derived types.

var cs = objectContext.CreateSet<B>().OfType<C>().ToList();

Just another example where generic approach doesn't work.

Edit:

I'm sorry, I see where the problem is. I was to fast in my description and I skipped one important detail of my solution. I don't have A (EntityBase) mapped because it is not needed. It is marked as abstract entity so it is not needed to have separate table for that which will be always empty in TPC inheritance. That is also a reason why my B (Person) doesn't call MapInheritedProperties in its mapping because I take it as a root of mapped inheritance. Because of that my DbSet defined for my example must be defined as:

public DbSet<Person> People { get; set; }

If you want to define DbSet<EntityBase> you must map Person in the same way as Employee:

modelBuilder.Entity<Person>().Map(m => m.MapInheritedProperties()).ToTable("Person"); 

Your current mapping defines EntityBase and Person as part of TPT inheritance with Employee as TPC inheritance but EF doesn't like combining inheritance types.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Thanks for the response, Ladislav. But now a get a different error. First thing i did is setting up the mapping for class B and C per your code above. the error is The type 'C' cannot be mapped as defined because it maps inherited properties from types that use entity splitting or another form of inheritance. Either choose a different inheritance mapping strategy so as to not map inherited properties, or change all types in the hierarchy to map inherited properties and to not use splitting. I know i have to read a lot about EF, code first etc. but i need to fix this and get this running asap. – Amit Jul 28 '11 at 18:32
  • Can you point me to a link / example which covers the exact scenario? I will work on refactoring my repository once the above issue is fixed. Thanks – Amit Jul 28 '11 at 18:44
  • You should probably post code of your entities because there must be some detail breaking the mapping or I pointed you to the wrong direction. – Ladislav Mrnka Jul 28 '11 at 18:57
  • Please see my actual entities above. – Amit Jul 28 '11 at 19:54
  • I understand having MapInheritedProperties() on person so that it Maps the properties from EntityBase in Person table. However, having MapInheritedProperties() on Employee duplicates the columns from Person table in Employee. And if i don't specify MapInheritedProperties() on Employee then it again becomes the combination of TPC and TPT. Right? – Amit Aug 01 '11 at 15:17