ferics2
ferics2

Reputation: 5432

Entity Framework Code First Many-to-Many relationship and inheritance

Forgive me if this question has been answered somewhere, I have been having a hard time finding a solution for this problem.

I am trying to set up EF Code First on an MVC4 Project. I have a User and Customer that both inherit from Person. I then have a Template object that has a Many-to-Many relationship with Customer and a One-to-Many relationship with User. Here is how I have it set up:

MODELS

public class Person
{
    [Key]
    public int PersonID { get; set; }

    public string LastName { get; set; }
    public string FirstName { get; set; }

    public string FullName
    {
        get
        {
            return String.Format("{0} {1}", FirstName, LastName);
        }
    }

    public string Email { get; set; }

    public virtual List<Template> Templates { get; set; }
}

public class User : Person
{
    .... 
}

public class Customer : Person
{
    ....
}

public class Template
{
    public int TemplateId { get; set; }
    public string TemplateName { get; set; }

    public virtual List<Customer> Customers { get; set; }

    [ForeignKey("User")]
    public int UserId { get; set; }
    public virtual User User { get; set; }
}

CONTEXT

public class ProjectContext : DbContext
{
    public ProjectContext()
        : base("name=ProjectDB")
    {
    }

    public DbSet<Template> Templates { get; set; }
    public DbSet<User> Users { get; set; }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions
            .Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<Template>()
            .HasMany(x => x.Customers)
            .WithMany(x => x.Templates)
            .Map(x => x.MapLeftKey("TemplateId")
                .MapRightKey("PersonId")
                .ToTable("TemplateCustomer")
            );
    }
}

If I remove the Person DBSet out of the context this works fine but sets up TPT inheritance. I would like to use TPH inheritance, but when I enable migrations with the Person DBSet in the context it chokes:

NavigationProperty 'Templates' is not valid. Type 'MvcProject.Models.Customer' of FromRole 'Template_Customers_Target' in AssociationType 'MvcProject.Models.Template_Customers' must exactly match with the type 'MvcProject.Models.Person' on which this NavigationProperty is declared on.

Where am I going wrong here?

Upvotes: 6

Views: 3074

Answers (2)

Slauma
Slauma

Reputation: 177133

You cannot inherit navigation properties from a base entity. They always must be declared in the class the other end of the relationship is refering to.

  • Template.Customers is refering to Customer (not to Person), hence the inverse navigation property Templates must be declared in Customer (not in Person)
  • Template.User is refering to User (not to Person), hence the inverse navigation property Templates must be declared in User (not in Person)

So, basically you must move the Templates collection from Person into both derived classes:

public class Person
{
    // no Templates collection here
}

public class User : Person
{
    //... 
    public virtual List<Template> Templates { get; set; }
}

public class Customer : Person
{
    //...
    public virtual List<Template> Templates { get; set; }
}

Then you can define the two relationships with Fluent API like so:

modelBuilder.Entity<Template>()
    .HasMany(t => t.Customers)
    .WithMany(c => c.Templates) // = Customer.Templates
    .Map(x => x.MapLeftKey("TemplateId")
               .MapRightKey("PersonId")
               .ToTable("TemplateCustomer"));

modelBuilder.Entity<Template>()
    .HasRequired(t => t.User)
    .WithMany(u => u.Templates) // = User.Templates
    .HasForeignKey(t => t.UserId);

Upvotes: 14

Moho
Moho

Reputation: 16498

Change your HasMany selector to People:

    modelBuilder.Entity<Template>()
        .HasMany(x => x.People) // here
        .WithMany(x => x.Templates)
        .Map(x => x.MapLeftKey("TemplateId")
            .MapRightKey("PersonId")
            .ToTable("TemplateCustomer")
        );

Upvotes: 0

Related Questions