Herencia y claves externas compuestas: una parte de la clave en la clase base, la otra parte en la clase derivada

Tengo problemas para crear una asignación de Entity Framework Code-First para el siguiente esquema de base de datos de ejemplo (en SQL Server):

Cada tabla contiene unaTenantId que forma parte de todas las claves primarias y externas (compuestas) (tenencia múltiple).

A Company es o bien unCustomer o unSupplier y trato de modelar esto a través del mapeo de herencia de tabla por tipo (TPT):

public abstract class Company
{
    public int TenantId { get; set; }
    public int CompanyId { get; set; }

    public int AddressId { get; set; }
    public Address Address { get; set; }
}

public class Customer : Company
{
    public string CustomerName { get; set; }

    public int SalesPersonId { get; set; }
    public Person SalesPerson { get; set; }
}

public class Supplier : Company
{
    public string SupplierName { get; set; }
}

Mapeo con Fluent API:

modelBuilder.Entity<Company>()
    .HasKey(c => new { c.TenantId, c.CompanyId });

modelBuilder.Entity<Customer>()
    .ToTable("Customers");

modelBuilder.Entity<Supplier>()
    .ToTable("Suppliers");

La mesa baseCompanies tiene una relación de uno a muchos con unaAddress (cada compañía tiene una dirección, no importa si es cliente o proveedor) y puedo crear un mapeo para esta asociación:

 modelBuilder.Entity<Company>()
     .HasRequired(c => c.Address)
     .WithMany()
     .HasForeignKey(c => new { c.TenantId, c.AddressId });

La clave externa se compone de una parte de la clave principal:TenantId - y una columna separada - laAddressId. Esto funciona.

Como puede ver en el esquema de la base de datos, desde la perspectiva de la base de datos, la relación entreCustomer yPerson es básicamente el mismo tipo de relación uno a muchos que entreCompany yAddress - La clave foránea se compone de nuevo de laTenantId (parte de la clave primaria) y la columnaSalesPersonId. (Solo un cliente tiene una persona de ventas, no unSupplier, por lo tanto, la relación está en la clase derivada esta vez, no en la clase base.)

Intento crear una asignación para esta relación con Fluent API de la misma manera que antes:

modelBuilder.Entity<Customer>()
    .HasRequired(c => c.SalesPerson)
    .WithMany()
    .HasForeignKey(c => new { c.TenantId, c.SalesPersonId });

Pero cuando EF intenta compilar el modelo unInvalidOperationException es aventado:

El componente de clave foránea 'TenantId' no es una propiedad declarada en el tipo 'Cliente'. Verifique que no se haya excluido explícitamente del modelo y que sea una propiedad primitiva válida.

Aparentemente no puedo componer una clave foránea a partir de una propiedad en la clase base y de otra propiedad en la clase derivada (aunque en el esquema de la base de datos la clave foránea está compuesta por columnas)ambos en la tabla del tipo derivadoCustomer).

Intenté dos modificaciones para que funcione quizás:

Cambió la asociación de clave externa entreCustomer yPerson a una asociación independiente, es decir, eliminó la propiedadSalesPersonId, y luego probé el mapeo:

modelBuilder.Entity<Customer>()
    .HasRequired(c => c.SalesPerson)
    .WithMany()
    .Map(m => m.MapKey("TenantId", "SalesPersonId"));

No ayuda (realmente no lo esperaba, lo haría) y la excepción es:

El esquema especificado no es válido. ... Cada nombre de propiedad en un tipo debe ser único. El nombre de propiedad 'TenantId' ya estaba definido.

Se cambió la asignación de TPT a TPH, es decir, se eliminaron los dosToTable llamadas Pero lanza la misma excepción.

Veo dos soluciones:

Introducir unSalesPersonTenantId en elCustomer clase:

public class Customer : Company
{
    public string CustomerName { get; set; }

    public int SalesPersonTenantId { get; set; }
    public int SalesPersonId { get; set; }
    public Person SalesPerson { get; set; }
}

y el mapeo:

modelBuilder.Entity<Customer>()
    .HasRequired(c => c.SalesPerson)
    .WithMany()
    .HasForeignKey(c => new { c.SalesPersonTenantId, c.SalesPersonId });

He probado esto y funciona. Pero tendré una nueva columna.SalesPersonTenantId en elCustomers mesa además de laTenantId. Esta columna es redundante porque ambas columnas siempre deben tener el mismo valor desde la perspectiva del negocio.

Abandonar la asignación de herencia y crear asignaciones uno a uno entreCompany yCustomer y entreCompany ySupplier. Company debe convertirse en un tipo concreto, no abstracto y tendría dos propiedades de navegación enCompany. Pero este modelo no expresaría correctamente que una empresa esya sea Un clienteo Un proveedor y no puede ser ambos al mismo tiempo. No lo probé pero creo que funcionaría.

Pegue el ejemplo completo que probé con (aplicación de consola, referencia al ensamblaje de EF 4.3.1, descargado a través de NuGet) aquí si a alguien le gusta experimentar con él:

using System;
using System.Data.Entity;

namespace EFTPTCompositeKeys
{
    public abstract class Company
    {
        public int TenantId { get; set; }
        public int CompanyId { get; set; }

        public int AddressId { get; set; }
        public Address Address { get; set; }
    }

    public class Customer : Company
    {
        public string CustomerName { get; set; }

        public int SalesPersonId { get; set; }
        public Person SalesPerson { get; set; }
    }

    public class Supplier : Company
    {
        public string SupplierName { get; set; }
    }

    public class Address
    {
        public int TenantId { get; set; }
        public int AddressId { get; set; }

        public string City { get; set; }
    }

    public class Person
    {
        public int TenantId { get; set; }
        public int PersonId { get; set; }

        public string Name { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Company> Companies { get; set; }
        public DbSet<Address> Addresses { get; set; }
        public DbSet<Person> Persons { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Company>()
                .HasKey(c => new { c.TenantId, c.CompanyId });

            modelBuilder.Entity<Company>()
                .HasRequired(c => c.Address)
                .WithMany()
                .HasForeignKey(c => new { c.TenantId, c.AddressId });

            modelBuilder.Entity<Customer>()
                .ToTable("Customers");

            // the following mapping doesn't work and causes an exception
            modelBuilder.Entity<Customer>()
                .HasRequired(c => c.SalesPerson)
                .WithMany()
                .HasForeignKey(c => new { c.TenantId, c.SalesPersonId });

            modelBuilder.Entity<Supplier>()
                .ToTable("Suppliers");

            modelBuilder.Entity<Address>()
                .HasKey(a => new { a.TenantId, a.AddressId });

            modelBuilder.Entity<Person>()
                .HasKey(p => new { p.TenantId, p.PersonId });
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
            using (var ctx = new MyContext())
            {
                try
                {
                    ctx.Database.Initialize(true);
                }
                catch (Exception e)
                {
                    throw;
                }
            }
        }
    }
}

Pregunta: ¿Hay alguna forma de asignar el esquema de base de datos anterior a un modelo de clase con Entity Framework?

Respuestas a la pregunta(3)

Su respuesta a la pregunta