Andrew
Andrew

Reputation: 6514

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?

Upvotes: 3

Views: 1572

Answers (2)

Rob G
Rob G

Reputation: 74

I know its a long way late, but hopefully may help other readers.

Although Ladislav was correct that using a mapped Foreign Key is not supported in EF6, I did find a useful workaround.

It is possible to define a computed column specification whose expression simply refers to the original column. Userid in the description above. This can be used as the discriminator for the TPH mapping. With this approach, the column need not be persisted, but can be used for TPH, with the original column being available for use as a foreign key.

Upvotes: 0

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364369

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.

Upvotes: 2

Related Questions