Vererbung und zusammengesetzte Fremdschlüssel - ein Teil des Schlüssels in der Basisklasse, der andere Teil in der abgeleiteten Klasse

Ich habe Probleme beim Erstellen einer Code-First-Zuordnung für Entity Framework für das folgende Beispieldatenbankschema (in SQL Server):

Jede Tabelle enthält eineTenantId Dies ist Teil aller (zusammengesetzten) Primär- und Fremdschlüssel (Multi-Tenancy).

A Company ist entweder aCustomer oder einSupplier und ich versuche, dies über Table-Per-Type (TPT) -Vererbungszuordnung zu modellieren:

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

Mapping mit Fluent API:

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

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

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

Der BasistischCompanies hat eine Eins-zu-Viele-Beziehung zuAddress (jede Firma hat eine Adresse, egal ob Kunde oder Lieferant) und ich kann ein Mapping für diese Assoziation erstellen:

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

Der Fremdschlüssel besteht aus einem Teil des Primärschlüssels - demTenantId - und eine separate Spalte - dieAddressId. Das funktioniert.

Wie Sie im Datenbankschema sehen können, ist aus Datenbanksicht die Beziehung zwischenCustomer undPerson ist im Grunde die gleiche Art von Eins-zu-Viele-Beziehung wie zwischenCompany undAddress - Der Fremdschlüssel setzt sich wieder aus dem zusammenTenantId (Teil des Primärschlüssels) und der SpalteSalesPersonId. (Nur ein Kunde hat einen Verkäufer, nicht einenSupplierDaher befindet sich die Beziehung dieses Mal in der abgeleiteten Klasse und nicht in der Basisklasse.)

Ich versuche, auf die gleiche Weise wie zuvor ein Mapping für diese Beziehung mit Fluent API zu erstellen:

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

Aber wenn EF versucht, das Modell zu kompilierenInvalidOperationException ist geworfen:

Die Fremdschlüsselkomponente 'TenantId' ist keine deklarierte Eigenschaft des Typs 'Customer'. Stellen Sie sicher, dass es nicht explizit aus dem Modell ausgeschlossen wurde und dass es sich um eine gültige primitive Eigenschaft handelt.

Anscheinend kann ich keinen Fremdschlüssel aus einer Eigenschaft in der Basisklasse und aus einer anderen Eigenschaft in der abgeleiteten Klasse erstellen (obwohl der Fremdschlüssel im Datenbankschema aus Spalten besteht)beide in der Tabelle des abgeleiteten TypsCustomer).

Ich habe zwei Modifikationen versucht, um es zum Laufen zu bringen:

Änderte die Fremdschlüsselzuordnung zwischenCustomer undPerson zu einer unabhängigen Vereinigung, d. h. die Eigenschaft entferntSalesPersonIdund probierte dann das Mapping:

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

Es hilft nicht (ich habe nicht wirklich gehofft, es würde) und die Ausnahme ist:

Das angegebene Schema ist ungültig. ... Jeder Eigenschaftsname in einem Typ muss eindeutig sein. Der Eigenschaftsname 'TenantId' wurde bereits definiert.

Die TPT-zu-TPH-Zuordnung wurde geändert, d. H. Die beiden wurden entferntToTable Anrufe. Aber es wirft die gleiche Ausnahme.

Ich sehe zwei Problemumgehungen:

Stellen Sie einSalesPersonTenantId in dieCustomer Klasse:

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

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

und das Mapping:

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

Ich habe das getestet und es funktioniert. Aber ich werde eine neue Kolumne habenSalesPersonTenantId in demCustomers Tisch zusätzlich zu derTenantId. Diese Spalte ist redundant, da beide Spalten aus geschäftlicher Sicht immer den gleichen Wert haben müssen.

Brechen Sie die Vererbungszuordnung ab und erstellen Sie Eins-zu-Eins-Zuordnungen zwischenCompany undCustomer und dazwischenCompany undSupplier. Company muss dann ein konkreter typ werden, nicht abstrakt und ich hätte zwei navigationseigenschaften inCompany. Aber dieses Modell würde nicht korrekt ausdrücken, dass ein Unternehmen istentweder ein Kundeoder ein Lieferant und kann nicht beides gleichzeitig sein. Ich habe es nicht getestet, aber ich glaube, es würde funktionieren.

Ich füge das vollständige Beispiel, mit dem ich getestet habe (Konsolenanwendung, Referenz auf EF 4.3.1-Assembly, über NuGet heruntergeladen), hier ein, wenn jemand damit experimentieren möchte:

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

Frage: Gibt es eine Möglichkeit, das obige Datenbankschema einem Klassenmodell mit Entity Framework zuzuordnen?

Antworten auf die Frage(3)

Ihre Antwort auf die Frage