Search code examples
c#entity-frameworkentity-framework-4.1ef-code-first

Difficulty Concerning EF Code First Fluent API, TPH, and Foreign Keys


I have two tables in my database. One is called Users, and the other is called Widgets. The Widgets table represents 3 entities in my code model. One of the entities, Widget, is a parent class for the other two entities, WidgetTypeA and WidgetTypeB. Both WidgetTypeA and WidgetTypeB have navigation properties to the User entity, which is persisted to the Users table in the database. I'm having trouble getting Code First to use the same foreign key for both the WidgetTypeA and WidgetTypeB entities (UserId). Does anyone know how to do this? It seems like it should be a common problem with Table Per Hierarchy mapping.

My entity classes are as follows:

public class Widget
{
    public int Id { get; set; }

    public string Name { get; set; }
}

class WidgetMap : EntityTypeConfiguration<Widget>
{
    public WidgetMap()
    {
        ToTable("Widgets");

        HasKey(w => w.Id);
        Property(w => w.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        Property(w => w.Name)
            .IsRequired()
            .HasMaxLength(75)
            .IsUnicode(true);
    }
}

public class WidgetTypeA : Widget
{
    public int UserId { get; set; }
    public virtual User User { get; set; }

    public string Color { get; set; }
    public int DepthLevel { get; set; }
}

class WidgetTypeAMap : EntityTypeConfiguration<WidgetTypeA>
{
    public WidgetTypeAMap()
    {
        Map(w => w.Requires("WidgetTypeId").HasValue(1));

        HasRequired(w => w.User)
            .WithMany(u => u.WidgetTypeAs)
            .HasForeignKey(w => w.UserId)
            .WillCascadeOnDelete(false);

        Property(w => w.Color)
            .IsOptional()
            .IsUnicode(true)
            .HasMaxLength(75);

        Property(w => w.DepthLevel)
            .IsOptional();
    }
}

public class WidgetTypeB : Widget
{
    public int UserId { get; set; }
    public virtual User User { get; set; }
}

class WidgetTypeBMap : EntityTypeConfiguration<WidgetTypeB>
{
    public WidgetTypeBMap()
    {
        Map(w => w.Requires("WidgetTypeId").HasValue(2));

        HasRequired(w => w.User)
            .WithMany(u => u.WidgetTypeBs)
            .HasForeignKey(w => w.UserId)
            .WillCascadeOnDelete(false);
    }
}

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int Age { get; set; }

    public virtual ICollection<WidgetTypeA> WidgetTypeAs { get; set; }
    public virtual ICollection<WidgetTypeB> WidgetTypeBs { get; set; }
}

class UserMap : EntityTypeConfiguration<User>
{
    public UserMap()
    {
        ToTable("Users");

        HasKey(u => u.Id);

        Property(u => u.Username)
            .IsRequired()
            .HasMaxLength(75)
            .IsUnicode(true);

        Property(u => u.Age)
            .IsRequired();
    }
}

At any rate, I keep getting the error

Invalid column name 'UserId1'

when I try to perform the following operations:

using (var entities = new MyEntities())
{
    User u = new User
    {
        Username = "Frank",
        Age = 14
    };
    entities.Users.Add(u);
    entities.SaveChanges();

    WidgetTypeA wa1 = new WidgetTypeA
    {
        Name = "0SDF81",
        UserId = u.Id,
        DepthLevel = 6
    };
    entities.WidgetTypeAs.Add(wa1);
    entities.SaveChanges();
}

Not sure if this can be fixed or not. I can always specify a second UserId foreign key for the Widgets table, but that seems pointless. Perhaps there's a way to do this using Fluent API?


Solution

  • You cannot map properties defined in different derived entities to the same column. That is limitation in EF. If your WidgetTypeA has UserId property and your WidgetTypeB has UserId property they must be different columns in the database. It should work if you move both UserId and User properties from derived types to the parent Widget type.