Szukaj…


Wprowadzenie

W tym temacie omówiono mapowanie relacji typu jeden do jednego za pomocą Entity Framework.

Mapowanie jeden na zero lub jeden

Powiedzmy jeszcze raz, że masz następujący model:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }
}

public class MyDemoContext : DbContext
{
  public DbSet<Person> People { get; set; }
  public DbSet<Car> Cars { get; set; }
}

A teraz chcesz to skonfigurować, aby móc wyrazić następującą specyfikację: jedna osoba może mieć jeden lub zero samochodów, a każdy samochód należy dokładnie do jednej osoby (relacje są dwukierunkowe, więc jeśli CarA należy do PersonA, wówczas PersonA jest właścicielem „CarA).

Zmodyfikujmy więc nieco model: dodaj właściwości nawigacji i właściwości klucza obcego:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
  public int CarId { get; set; }
  public virtual Car Car { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }
  public int PersonId { get; set; }
  public virtual Person Person { get; set; }
}

I konfiguracja:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Person).WithOptional(p => p.Car);                        
  }
}    

Do tego czasu powinno to być oczywiste. Samochód ma wymaganą osobę ( HasRequired () ), a osoba ma opcjonalny samochód ( WithOptional () ). Ponownie, nie ma znaczenia, z której strony konfigurujesz tę relację, po prostu zachowaj ostrożność, używając właściwej kombinacji Has / With i Required / Optional. Od strony Person wyglądałoby to tak:

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
     this.HasOptional(p => p.Car).WithOptional(c => c.Person);                        
  }
}    

Teraz sprawdźmy schemat db:

Przyjrzyj się uważnie: widać, że w aplikacji People nie ma FK, który mógłby odnosić się do Car . Ponadto FK w Car nie jest PersonId , ale CarId . Oto rzeczywisty skrypt dla FK:

ALTER TABLE [dbo].[Cars]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])

Oznacza to, że kluczowe właściwości CarId i PersonId , które mamy w modelu, są w zasadzie ignorowane. Są w bazie danych, ale nie są to klucze obce, jak można się spodziewać. Wynika to z faktu, że mapowania jeden do jednego nie obsługują dodawania FK do modelu EF. A to dlatego, że mapowania jeden na jeden są dość problematyczne w relacyjnej bazie danych.

Chodzi o to, że każda osoba może mieć dokładnie jeden samochód, a ten samochód może należeć tylko do tej osoby. Lub mogą istnieć dane osobowe, które nie mają przypisanych samochodów.

Jak więc można to przedstawić za pomocą kluczy obcych? Oczywiście, nie może być PersonId w Car , a CarId w People . Aby wymusić, że każda osoba może mieć tylko jeden samochód, PersonId musiałby być unikalny w Car . Ale jeśli PersonId jest unikalny w People , to jak możesz dodać dwa lub więcej rekordów, w których PersonId ma NULL (więcej niż jeden samochód, który nie ma właścicieli)? Odpowiedź: nie możesz (cóż, właściwie możesz utworzyć filtrowany unikalny indeks w SQL Server 2008 i nowszych wersjach, ale na chwilę zapomnijmy o tej technice; nie wspominając o innych RDBMS). Nie wspominając o przypadku, w którym określasz oba końce relacji ...

Jedyny prawdziwy sposób na wymuszenie tej reguły, jeśli tabele People i Car mają „ten sam” klucz podstawowy (te same wartości w połączonych rekordach). Aby to zrobić, CarId in Car musi być zarówno PK, jak i FK dla PK People. A to sprawia, że cały schemat jest bałaganem. Kiedy go używam, raczej nazywam PK / FK w Car PersonId i odpowiednio go konfiguruję:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }        
  public virtual Car Car { get; set; }
}

public class Car
{        
  public string LicensePlate { get; set; }
  public int PersonId { get; set; }
  public virtual Person Person { get; set; }
}

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Person).WithOptional(p => p.Car);
     this.HasKey(c => c.PersonId);
  }
}

Nie idealne, ale może nieco lepsze. Mimo to musisz zachować ostrożność, korzystając z tego rozwiązania, ponieważ jest ono sprzeczne ze zwykłymi konwencjami nazewnictwa, co może doprowadzić Cię na manowce. Oto schemat wygenerowany z tego modelu:

Tak więc relacja ta nie jest wymuszana przez schemat bazy danych, ale przez samą platformę Entity Framework. Dlatego musisz być bardzo ostrożny, gdy używasz tego, aby nikt nie hartował bezpośrednio z bazą danych.

Mapowanie jeden do jednego

Mapowanie jeden do jednego (gdy wymagane są obie strony) jest również trudną sprawą.

Wyobraźmy sobie, jak można to przedstawić za pomocą kluczy obcych. Ponownie, CarId w People , że odnosi się do CarId w Car , a PersonId w samochodzie, który odnosi się do PersonId w People .

Co się stanie, jeśli chcesz wstawić rekord samochodu? Aby to się udało, w tym rekordzie samochodu musi być określony PersonId , ponieważ jest on wymagany. Aby ten PersonId był ważny, musi istnieć odpowiedni rekord w People . OK, więc przejdźmy dalej i wstawmy rekord osoby. Ale aby to się udało, ważny CarId musi znajdować się w rejestrze osób - ale ten samochód nie jest jeszcze włożony! Nie może tak być, ponieważ najpierw musimy wstawić rekord osoby poleconej. Nie możemy jednak wstawić rekordu osoby poleconej, ponieważ odwołuje się on do rekordu samochodu, więc należy go najpierw wstawić (odbiór klucza obcego :)).

Dlatego też nie można tego przedstawić w sposób „logiczny”. Ponownie musisz upuścić jeden z kluczy obcych. Który upuścisz zależy od ciebie. Strona, która pozostała z kluczem obcym, nazywana jest „zależną”, a strona, która pozostała bez klucza obcego, jest nazywana „główną”. I znowu, aby zapewnić unikalność w zależności, PK musi być FK, więc dodanie kolumny FK i importowanie jej do modelu nie jest obsługiwane.

Oto konfiguracja:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
    this.HasRequired(c => c.Person).WithRequiredDependent(p => p.Car);
    this.HasKey(c => c.PersonId);
  }
}

Do tej pory naprawdę powinieneś zrozumieć logikę :) Pamiętaj tylko, że możesz również wybrać drugą stronę, po prostu zachowaj ostrożność przy korzystaniu z wersji Dependent / Principal WithRequired (i nadal musisz skonfigurować PK w samochodzie).

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
    this.HasRequired(p => p.Car).WithRequiredPrincipal(c => c.Person);
  }
}

Jeśli sprawdzisz schemat DB, przekonasz się, że jest dokładnie taki sam, jak w przypadku rozwiązania jeden do jednego lub zero. To dlatego, że znowu nie jest to wymuszone przez schemat, ale przez sam EF. Więc znowu uważaj :)

Mapowanie jednego lub zera na jeden lub zero

Na zakończenie spójrzmy pokrótce, kiedy obie strony są opcjonalne.

Do tej pory powinieneś naprawdę się nudzić tymi przykładami :), więc nie wchodzę w szczegóły i nie bawię się pomysłem posiadania dwóch FK-ów i potencjalnych problemów i ostrzegam cię przed niebezpieczeństwami wynikającymi z nieprzestrzegania tych zasad w schemat, ale w samym EF.

Oto konfiguracja, którą musisz zastosować:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
    this.HasOptional(c => c.Person).WithOptionalPrincipal(p => p.Car);
    this.HasKey(c => c.PersonId);
  }
}

Znowu możesz konfigurować również z drugiej strony, po prostu zachowaj ostrożność, używając właściwych metod :)



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow