16

Mam problemy z utworzeniem kodu Entity Framework-First dla poniższego przykładowego schematu bazy danych (w SQL Server):Klucze obce dziedziczenia i złożone - jedna część klucza w klasie bazowej, druga część w klasie pochodnej

Database schema with composite foreign keys

Każda tabela zawiera TenantId która jest częścią wszystkich (kompozytowe) podstawowych i kluczy obcych (Multi-dzierżawy).

Company jest albo Customer lub Supplier i próbie tego modelu przez Tabela Per-type (TPT) mapowania spadku:

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

mapowania z płynną API

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

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

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

Podstawowa tabela Companies ma relację jeden-do-wielu z Address (każda firma ma adres, niezależnie od tego, czy klient lub dostawca) i mogę utworzyć mapowanie dla tego powiązania:

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 - w TenantId - oraz oddzielnej kolumnie - w AddressId. To działa.

Jak widać na schemacie bazy danych, z punktu widzenia bazy relacje między Customer i Person jest w zasadzie taki sam rodzaj relacji jeden-do-wielu, jak między Company i Address - klucz obcy składa się ponownie z TenantId (część klucza podstawowego) i kolumna SalesPersonId. (Tylko klient ma sprzedawcę, a nie Supplier, dlatego relacja jest w klasie pochodnej tym razem, nie w klasie bazowej).

Próbuję utworzyć mapowanie dla tej relacji z Fluent API w ten sam sposób jak poprzednio:

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

Ale kiedy EF próbuje skompilować model InvalidOperationException jest wyrzucany:

zagraniczny kluczowym elementem „TenantId” nie jest zadeklarowana nieruchomość na typu „klient”. Sprawdź, czy nie został jawnie wykluczony z modelu i czy jest poprawną właściwością pierwotną.

Widocznie nie mogę komponować klucz obcy z właściwości w klasie bazowej i innego mienia w klasie pochodnej (choć w schemacie bazy danych kluczem obcym składa się z kolumn zarówno w tabeli typu pochodnego za Customer).

próbowałem dwie modyfikacje, aby to działa może:

  • Zmieniono klucza obcego stowarzyszenie między Customer i Person do niezależnego stowarzyszenia, tjusunięte własnością SalesPersonId, a następnie próbował mapowanie:

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

    to nie pomaga (ja naprawdę nie mam nadzieję, że będzie), a wyjątek jest:

    Schema określony nie jest ważny . ... Każda nazwa właściwości w typie musi być unikalna. Nazwa właściwości "TenantId" została już zdefiniowana.

  • Zmieniono mapowanie TPT na TPH, tj. Usunięto dwa wywołania ToTable. Ale rzuca ten sam wyjątek.

widzę dwa obejścia:

  • wprowadzenia SalesPersonTenantId do klasy Customer:

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

    testowanego to i Prace. Ale będę mieć nową kolumnę SalesPersonTenantId w tabeli Customers oprócz TenantId. Ta kolumna jest zbędna, ponieważ obie kolumny zawsze muszą mieć tę samą wartość z perspektywy biznesowej.

  • porzucić mapowania spadków i tworzą jeden-do-jednego między Company mapowania i Customer oraz pomiędzy Company i Supplier. Company musi wtedy stać się typem konkretnym, a nie abstrakcyjnym, a ja miałbym dwie właściwości nawigacji w Company. Jednak model ten nie wyraża poprawnie, że firma jest dostawcą albo lub i nie może być jednocześnie obu. Nie testowałem tego, ale uważam, że zadziała.

wkleić pełny przykład (testowałem z aplikacji konsoli, odniesienie do EF 4.3.1 montażu, pobrane poprzez Nuget) w tutaj, jeśli ktoś lubi eksperymentować z nim:

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 na odwzorowanie schematu bazy danych powyżej na model klasy z Entity Framework?

+0

Dlaczego tego potrzebujesz? Dlaczego nie mieć tylko jednej kolumny Id i uniknąć bólu głowy? – ivowiblo

+0

@ivowiblo: Ponieważ chcę zapewnić w bazie danych, że relacje między wierszami w różnych tabelach zawsze odnoszą się do danych tego samego dzierżawcy. Gdybym miał jedną kolumnę Id, mógłbym mieć klienta najemcy 1, odnoszącego się do SalesPerson najemcy 2, który byłby ważny w DB. Musiałem się upewnić, że tak się nie stanie na poziomie aplikacji. Błąd byłby despotyczny (dzierżawca 1 może uzyskać dostęp do danych najemcy 2). Niemniej jednak twoja uwaga jest dobrym punktem i muszę ją rozważyć, jeśli nie ma innego rozwiązania. – Slauma

+0

@Slauma: Cześć, znalazłeś jakieś rozwiązanie lub obejście problemu? Mam ten sam problem i nie wiem, jak postępować. Próbowałem też np. dołącz wersję override dla int TenantId do klasy Customer. Ale nadal nie działa. – iuristona

Odpowiedz

6

Cóż, nie mogę nic komentować, więc dodam to jako odpowiedź.

Utworzyłem problem w CodePlex dla tego problemu, więc mam nadzieję, że wkrótce się nim zajrzą. Bądźcie czujni!

http://entityframework.codeplex.com/workitem/865


wyniku emisji na CodePlex (który został zamknięty w międzyczasie) jest to, że scenariusz w pytaniu nie jest obsługiwane i są obecnie brak planów wspierania go w najbliższej przyszłości .

Cytat zespołu Entity Framework na CodePlex:

Jest to część bardziej fundamentalne ograniczenia gdzie EF nie obsługuje posiadające właściwości zdefiniowane w typie bazowym, a następnie używając go jako zagraniczny klucz w typie pochodnym. Niestety jest to ograniczenie, które bardzo trudno byłoby usunąć z naszej bazy kodowej . Biorąc pod uwagę, że nie dostaliśmy wielu żądań, nie jest to coś, co planujemy na ten czas adresować, dlatego zamykamy ten problem.

+1

Dodałem wynik z CodePlex do odpowiedzi. – Slauma

0

Nie jest rozwiązaniem, ale obejściem problemu (*): dobrym wyborem jest użycie kolumn z pojedynczym identyfikatorem (as), zwykle automatycznie zwiększanych i zapewnienie integralności bazy danych za pomocą kluczy obcych, unikatowych indeksów itp. Bardziej złożone dane integralność można osiągnąć za pomocą wyzwalaczy, więc może uda Ci się jechać w ten sposób, ale możesz pozostawić to na poziomie logiki biznesowej aplikacji, chyba że aplikacja jest rzeczywiście skoncentrowana na danych. Ale skoro używasz Entity Framework, prawdopodobnie można bezpiecznie założyć, że to nie twoja sprawa ...?

(*) zgodnie z sugestią ivowiblo

+0

Jest to obejście, ale nie jest to dobra praktyka w mojej opinii. W rzeczywistości stanie się bardziej skomplikowana i rozwinie logikę biznesową (wyzwalacze mogą być po prostu nieprzyjemne do opracowania i debugowania). Sugerowałbym, że warto spróbować znaleźć rozwiązanie architektury danych dla tego problemu, zanim spróbujesz skompromitować relacyjną integralność bazy danych, aby ułatwić programowanie. – BenSwayne

0

myślę, że jest prostszy i zmniejsza złożoność mieć Table -> tableid (PK) -> inne kolumny w tym FKS.

W twoim przykładzie dodanie kolumny CustomerId do tabeli Klienci rozwiąże problem.

+2

AAAAARRRRGGGHHHHHH! Przepraszam :(Ale twoja odpowiedź brzmi tak: pytam "Chcę zbudować własną stację pogodową do domu, która może mierzyć temperaturę i ciśnienie barometryczne. Jak mogę to zrobić?", A następnie odpowiadasz: "Ciśnienie pomiarowe jest zbyt złożone Zbuduj domową stację pogodową, która może mierzyć tylko temperaturę, a to by rozwiązało twój problem. " – Slauma

+0

Naprawdę? W jaki sposób dodanie CustomerId zmniejsza twoją funkcjonalność? – PeteGO

+1

Jak to rozwiązuje problem? Jeśli masz na myśli pojedynczą kolumnę klucza zamiast klucza złożonego, przeczytaj dwa komentarze poniżej pytania, w którym opisałem powód, dla którego chcę mieć klucze złożone. Ale możliwe, że źle zrozumiałem twoją propozycję. – Slauma

Powiązane problemy