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
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
iPerson
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 klasyCustomer
: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 tabeliCustomers
opróczTenantId
. 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 iCustomer
oraz pomiędzyCompany
iSupplier
.Company
musi wtedy stać się typem konkretnym, a nie abstrakcyjnym, a ja miałbym dwie właściwości nawigacji wCompany
. 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?
Dlaczego tego potrzebujesz? Dlaczego nie mieć tylko jednej kolumny Id i uniknąć bólu głowy? – ivowiblo
@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
@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