Search code examples
entity-frameworkentity-framework-4.1ef-code-firstfluent-interface

misplaced foreign keys when using abstract classes and fluent api


I've a problem mapping foreign keys to the correct table when using abstract classes. Here's my model :

public abstract class Entity
{
    public Guid UID { get; set; }
}

public abstract class Product : Entity
{
    public DeviceModel Model { get; set; }
    public User Operator { get; set; }
}

public abstract class OrderEntry : Entity
{
    public Order Order { get; set; }
}

public class Device : Product
{
    public String Reference { get; set; }
    public String Serial { get; set; }
    public String SoftwareVersion { get; set; }
}

public class OrderEntryDevice : OrderEntry
{
    public DeviceModel Model { get; set; }
}

And the fluent api configurations (TPT schema) :

public class EntityConfiguration : EntityTypeConfiguration<Entity>
{
    public EntityConfiguration()
    {
        ToTable("Entities");

        HasKey(t => t.UID);
    }
}

public class ProductConfiguration : EntityTypeConfiguration<Product>
{
    public ProductConfiguration()
    {
        ToTable("Products");

        HasOptional(t => t.Operator)
            .WithMany()
            .Map(t => t.MapKey("FK_Operator"))
            .WillCascadeOnDelete(false);
    }
}

public class OrderEntryConfiguration : EntityTypeConfiguration<OrderEntry>
{
    public OrderEntryConfiguration()
    {
        ToTable("OrderEntries");

        HasRequired(t => t.Order)
            .WithMany()
            .Map(t => t.MapKey("FK_Order"))
            .WillCascadeOnDelete(false);
    }
}

public class DeviceConfiguration : EntityTypeConfiguration<Device>
{
    public DeviceConfiguration()
    {
        ToTable("Devices");

        Property(t => t.Reference)
            .IsRequired();

        Property(t => t.Serial)
            .IsRequired();

        HasRequired(t => t.Model)
            .WithMany()
            .Map(t => t.MapKey("FK_Model"))
            .WillCascadeOnDelete(false);
    }
}

public class OrderEntryDeviceConfiguration : EntityTypeConfiguration<OrderEntryDevice>
{
    public OrderEntryDeviceConfiguration()
    {
        ToTable("OrderEntriesDevice");

        HasRequired(t => t.Model)
            .WithMany()
            .Map(t => t.MapKey("FK_Model"))
            .WillCascadeOnDelete(false);
    }
}

Creation of the database will put the "FK_Operator" foreign key in the "Products" table (exactly where I want it) but the "FK_Order" foreign key is placed in the "Entities" table instead of "OrderEntries" table. If I change the abstract property of class "OrderEntry" to concrete, then everything is OK. Do I have to avoid abstract class int this case ?


Solution

  • I've tried your model and I cannot reproduce the problem. I get the FK_Order column in the OrderEntries table and not in Entities table - as expected.

    You can copy the following into Program.cs of a console app (also add EntityFramework.dll and System.ComponentModel.DataAnnotations.dll to references).

    I have created three dummy classes for User, DeviceModel and Order to get the code compiled and running. But the other classes are a copy from your question.

    The question is: Where is the important difference between the code below and your code which could cause the wrong mapping you have?

    using System;
    using System.Linq;
    using System.Data.Entity.ModelConfiguration;
    using System.Data.Entity;
    using System.ComponentModel.DataAnnotations;
    
    namespace EFAbstractTest
    {
        public class User
        {
            [Key]
            public Guid UID { get; set; }
            public string Name { get; set; }
        }
    
        public class DeviceModel
        {
            [Key]
            public Guid UID { get; set; }
            public string Name { get; set; }
        }
    
        public class Order
        {
            [Key]
            public Guid UID { get; set; }
            public string Name { get; set; }
        }
    
        public abstract class Entity
        {
            public Guid UID { get; set; }
        }
    
        public abstract class Product : Entity
        {
            public DeviceModel Model { get; set; }
            public User Operator { get; set; }
        }
    
        public abstract class OrderEntry : Entity
        {
            public Order Order { get; set; }
        }
    
        public class Device : Product
        {
            public String Reference { get; set; }
            public String Serial { get; set; }
            public String SoftwareVersion { get; set; }
        }
    
        public class OrderEntryDevice : OrderEntry
        {
            public DeviceModel Model { get; set; }
        }
    
        public class EntityConfiguration : EntityTypeConfiguration<Entity>
        {
            public EntityConfiguration()
            {
                ToTable("Entities");
    
                HasKey(t => t.UID);
            }
        }
    
        public class ProductConfiguration : EntityTypeConfiguration<Product>
        {
            public ProductConfiguration()
            {
                ToTable("Products");
    
                HasOptional(t => t.Operator)
                    .WithMany()
                    .Map(t => t.MapKey("FK_Operator"))
                    .WillCascadeOnDelete(false);
            }
        }
    
        public class OrderEntryConfiguration : EntityTypeConfiguration<OrderEntry>
        {
            public OrderEntryConfiguration()
            {
                ToTable("OrderEntries");
    
                HasRequired(t => t.Order)
                    .WithMany()
                    .Map(t => t.MapKey("FK_Order"))
                    .WillCascadeOnDelete(false);
            }
        }
    
        public class DeviceConfiguration : EntityTypeConfiguration<Device>
        {
            public DeviceConfiguration()
            {
                ToTable("Devices");
    
                Property(t => t.Reference)
                    .IsRequired();
    
                Property(t => t.Serial)
                    .IsRequired();
    
                HasRequired(t => t.Model)
                    .WithMany()
                    .Map(t => t.MapKey("FK_Model"))
                    .WillCascadeOnDelete(false);
            }
        }
    
        public class OrderEntryDeviceConfiguration : EntityTypeConfiguration<OrderEntryDevice>
        {
            public OrderEntryDeviceConfiguration()
            {
                ToTable("OrderEntriesDevice");
    
                HasRequired(t => t.Model)
                    .WithMany()
                    .Map(t => t.MapKey("FK_Model"))
                    .WillCascadeOnDelete(false);
            }
        }
    
        public class MyContext : DbContext
        {
            public DbSet<Entity> Entities { get; set; }
            public DbSet<User> Users { get; set; }
            public DbSet<DeviceModel> DeviceModels { get; set; }
            public DbSet<Order> Orders { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Configurations.Add(new EntityConfiguration());
                modelBuilder.Configurations.Add(new ProductConfiguration());
                modelBuilder.Configurations.Add(new OrderEntryConfiguration());
                modelBuilder.Configurations.Add(new DeviceConfiguration());
                modelBuilder.Configurations.Add(new OrderEntryDeviceConfiguration());
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                using (var ctx = new MyContext())
                {
                    // some query, just to trigger database creation
                    ctx.Orders.Count();
                }
            }
        }
    }