Dziedziczenie i złożone klucze obce - jedna część klucza w klasie bazowej, druga część w klasie pochodnej

Mam problemy z utworzeniem odwzorowania kodu pierwszego elementu Entity Framework dla następującego przykładowego schematu bazy danych (w programie SQL Server):

Każda tabela zawieraTenantId który jest częścią wszystkich (złożonych) kluczy podstawowych i zagranicznych (Multi-Tenancy).

A Company jest albo aCustomer lub aSupplier i staram się modelować to za pomocą mapowania dziedziczenia tabel na typ (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; }
}

Mapowanie za pomocą płynnego interfejsu API:

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

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

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

Tabela podstawowaCompanies ma relację jeden do wieluAddress (każda firma ma adres, niezależnie od tego, czy jest to klient czy dostawca) i mogę utworzyć mapowanie dla tego związku:

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

Klucz obcy składa się z jednej części klucza podstawowego -TenantId - i osobna kolumna -AddressId. To działa.

Jak widać w schemacie bazy danych, z perspektywy bazy danych relacja międzyCustomer iPerson jest zasadniczo tym samym rodzajem relacji jeden-do-wielu, jak międzyCompany iAddress - klucz obcy składa się ponownie z kluczaTenantId (część klucza podstawowego) i kolumnaSalesPersonId. (Tylko klient ma sprzedawcę, a nie sprzedawcęSupplier, zatem tym razem relacja jest w klasie pochodnej, a nie w klasie bazowej.)

Staram się stworzyć mapowanie dla tej relacji z Fluent API w taki sam sposób jak wcześniej:

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

Ale kiedy EF próbuje skompilować model anInvalidOperationException Jest rzucony:

Komponent klucza obcego „TenantId” nie jest zadeklarowaną właściwością typu „Klient”. Sprawdź, czy nie został jawnie wykluczony z modelu i że jest prawidłową właściwością pierwotną.

Najwyraźniej nie mogę skomponować klucza obcego z właściwości w klasie bazowej iz innej właściwości w klasie pochodnej (chociaż w schemacie bazy danych klucz obcy składa się z kolumnobie w tabeli typów pochodnychCustomer).

Wypróbowałem dwie modyfikacje, aby to działało:

Zmieniono powiązanie klucza obcego międzyCustomer iPerson niezależnemu stowarzyszeniu, tj. usunięciu nieruchomościSalesPersonId, a następnie wypróbowałem mapowanie:

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

To nie pomaga (tak naprawdę nie miałem nadziei, że tak będzie) i wyjątkiem jest:

Określony schemat jest nieprawidłowy. ... Każda nazwa właściwości w typie musi być unikalna. Nazwa właściwości „TenantId” została już zdefiniowana.

Zmieniono TPT na mapowanie TPH, tj. Usunięto obaToTable połączenia. Ale generuje ten sam wyjątek.

Widzę dwa obejścia:

Przedstaw aSalesPersonTenantId wCustomer klasa:

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

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

i mapowanie:

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

Przetestowałem to i działa. Ale będę miał nową kolumnęSalesPersonTenantId wCustomers stół opróczTenantId. Ta kolumna jest zbędna, ponieważ obie kolumny zawsze muszą mieć tę samą wartość z perspektywy biznesowej.

Porzuć mapowanie dziedziczenia i twórz indywidualne mapowania międzyCompany iCustomer i międzyCompany iSupplier. Company musi wtedy stać się konkretnym typem, a nie abstrakcją, a ja miałbym dwie właściwości nawigacyjneCompany. Ale ten model nie wyrażałby prawidłowo, że firma jestzarówno klientlub dostawcą i nie może być jednocześnie obu. Nie testowałem tego, ale wierzę, że to zadziała.

Wklejam tutaj pełny przykład, który przetestowałem (aplikacja konsoli, odwołanie do złożenia EF 4.3.1, pobrane za pośrednictwem NuGet), jeśli ktoś lubi eksperymentować z tym:

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;
                }
            }
        }
    }
}

Pytanie: Czy istnieje sposób mapowania schematu bazy danych powyżej na model klasy z Entity Framework?

questionAnswers(3)

yourAnswerToTheQuestion