Szukaj…


Wprowadzenie

W tym temacie omówiono sposób mapowania relacji jeden do wielu i wiele do wielu przy użyciu kodu Entity Framework Code First.

Mapowanie jeden do wielu

Powiedzmy, że masz dwa różne byty, coś takiego:

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

I chcesz ustawić relację jeden do wielu między nimi, to znaczy, że jedna osoba może mieć zero, jeden lub więcej samochodów, a jeden samochód należy dokładnie do jednej osoby. Każda relacja jest dwukierunkowa, więc jeśli dana osoba ma samochód, samochód należy do tej osoby.

W tym celu wystarczy zmodyfikować klasy modeli:

public class Person
{
   public int PersonId { get; set; }
   public string Name { get; set; }
   public virtual ICollection<Car> Cars { get; set; } // don't forget to initialize (use HashSet)
}

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 to wszystko :) Masz już skonfigurowany związek. W bazie danych jest to oczywiście reprezentowane przez klucze obce.

Mapowanie jeden do wielu: wbrew konwencji

W ostatnim przykładzie widać, że EF określa, która kolumna jest kluczem obcym i gdzie powinien wskazywać. W jaki sposób? Korzystając z konwencji. Posiadanie właściwości typu Person o nazwie Person z właściwością PersonId prowadzi EF do wniosku, że PersonId jest kluczem obcym i wskazuje na klucz podstawowy tabeli reprezentowany przez typ Person .

Ale co, jeśli zmienisz PersonId na OwnerId i Person na Owner w typie samochodu ?

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

Niestety, w tym przypadku konwencje nie są wystarczające do wygenerowania poprawnego schematu DB:

Bez obaw; możesz pomóc EF z kilkoma wskazówkami na temat twoich relacji i kluczy w modelu. Wystarczy skonfigurować typ Car aby użyć właściwości OwnerId jako FK. Utwórz konfigurację typu encji i zastosuj ją w swojej OnModelCreating() :

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Owner).WithMany(p => p.Cars).HasForeignKey(c => c.OwnerId);
  }
}

Mówi to w zasadzie, że Car ma wymaganą właściwość Owner ( HasRequired () ), a w typie Owner właściwość Cars służy do odwoływania się do encji car ( WithMany () ). Na koniec określono właściwość reprezentującą klucz obcy ( HasForeignKey () ). To daje nam pożądany schemat:

Możesz również skonfigurować relację ze strony Person :

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
    this.HasMany(p => p.Cars).WithRequired(c => c.Owner).HasForeignKey(c => c.OwnerId);
  }
}

Pomysł jest taki sam, tylko boki są różne (zwróć uwagę, jak możesz przeczytać całość: „ta osoba ma wiele samochodów, każdy z wymaganym właścicielem”). Nie ma znaczenia, czy konfigurujesz relację od strony Person czy Car . Możesz nawet uwzględnić oba, ale w tym przypadku należy zachować ostrożność, aby określić tę samą relację po obu stronach!

Mapowanie zera lub jeden do wielu

W poprzednich przykładach samochód nie może istnieć bez osoby. Co jeśli chcesz, aby osoba była opcjonalna od strony samochodu? Cóż, jest to dość łatwe, wiedząc, jak zrobić jeden do wielu. Po prostu zmień PersonId in Car na null:

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

A następnie użyj HasOptional () (lub WithOptional () , w zależności od strony, z której wykonujesz konfigurację):

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasOptional(c => c.Owner).WithMany(p => p.Cars).HasForeignKey(c => c.OwnerId);
  }
}

Wiele do wielu

Przejdźmy do innego scenariusza, w którym każda osoba może mieć wiele samochodów, a każdy samochód może mieć wielu właścicieli (ale znowu związek jest dwukierunkowy). Jest to relacja wiele do wielu. Najprostszym sposobem jest pozwolenie EF na magię przy użyciu konwencji.

Po prostu zmień model w ten sposób:

 public class Person
{
   public int PersonId { get; set; }
   public string Name { get; set; }
   public virtual ICollection<Car> Cars { get; set; }
}

public class Car
{
   public int CarId { get; set; }
   public string LicensePlate { get; set; }        
   public virtual ICollection<Person> Owners { get; set; }
}

I schemat:

Prawie idealnie. Jak widać, EF uznał potrzebę stworzenia stolika do łączenia, w którym można śledzić parowania osoba-samochód.

Wiele do wielu: dostosowywanie tabeli łączenia

Możesz zmienić nazwę pól w tabeli łączenia, aby była nieco bardziej przyjazna. Możesz to zrobić przy użyciu zwykłych metod konfiguracji (ponownie, nie ma znaczenia, z której strony wykonujesz konfigurację):

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
   public CarEntityTypeConfiguration()
   {
      this.HasMany(c => c.Owners).WithMany(p => p.Cars)
          .Map(m =>
              {
                 m.MapLeftKey("OwnerId");
                 m.MapRightKey("CarId");
                 m.ToTable("PersonCars");
              }
        );
  }
}

Dość łatwy do odczytania nawet: ten samochód ma wielu właścicieli ( HasMany () ), a każdy właściciel ma wiele samochodów ( WithMany () ). Zamapuj to, aby odwzorować lewy klucz na OwnerId ( MapLeftKey () ), prawy klucz na CarId ( MapRightKey () ) i całą rzecz na tabelę PersonCars ( ToTable () ). A to daje dokładnie ten schemat:

Wiele do wielu: niestandardowy element złączenia

Muszę przyznać, że tak naprawdę nie jestem fanem pozwalania EF wnioskować o tabeli dołączeń bez elementu dołączającego. Nie można śledzić dodatkowych informacji do powiązania samochód-osoba (powiedzmy, od której daty jest ona ważna), ponieważ nie można modyfikować tabeli.

Ponadto CarId w tabeli łączenia jest częścią klucza podstawowego, więc jeśli rodzina kupi nowy samochód, musisz najpierw usunąć stare skojarzenia i dodać nowe. EF ukrywa to przed tobą, ale oznacza to, że musisz wykonać te dwie operacje zamiast prostej aktualizacji (nie wspominając o tym, że częste wstawianie / usuwanie może prowadzić do fragmentacji indeksu - dobrze, że można to łatwo naprawić ).

W takim przypadku możesz utworzyć podmiot dołączający, który będzie zawierał odniesienie zarówno do jednego określonego samochodu, jak i jednej konkretnej osoby. Zasadniczo patrzysz na swoje skojarzenie wiele do wielu jako kombinacje dwóch skojarzeń jeden do wielu:

public class PersonToCar
{
   public int PersonToCarId { get; set; }
   public int CarId { get; set; }
   public virtual Car Car { get; set; }
   public int PersonId { get; set; }
   public virtual Person Person { get; set; }
   public DateTime ValidFrom { get; set; }
}

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
  public virtual ICollection<PersonToCar> CarOwnerShips { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }        
  public virtual ICollection<PersonToCar> Ownerships { get; set; }
}

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

To daje mi znacznie większą kontrolę i jest o wiele bardziej elastyczny. Mogę teraz dodawać niestandardowe dane do powiązania, a każde skojarzenie ma swój własny klucz podstawowy, dzięki czemu mogę zaktualizować w nim samochód lub referencję właściciela.

Zauważ, że tak naprawdę jest to tylko połączenie dwóch relacji jeden do wielu, więc możesz użyć wszystkich opcji konfiguracji omówionych w poprzednich przykładach.



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