Code First: Niezależne stowarzyszenia czy stowarzyszenia kluczy obcych?

102

Prowadzę ze sobą mentalną debatę za każdym razem, gdy zaczynam pracę nad nowym projektem i projektuję swoje POCO. Widziałem wiele samouczków / przykładów kodu, które wydają się faworyzować skojarzenia kluczy obcych :

Skojarzenie klucza obcego

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; } // <-- Customer ID
    ...
}

W przeciwieństwie do niezależnych stowarzyszeń :

Niezależne stowarzyszenie

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

W przeszłości pracowałem z NHibernate i korzystałem z niezależnych skojarzeń, które nie tylko sprawiają wrażenie bardziej OO, ale także (z leniwym ładowaniem) mają tę zaletę, że dają mi dostęp do całego obiektu klienta, zamiast tylko jego identyfikatora. Pozwala mi to na przykład pobrać instancję Order, a następnie zrobić to Order.Customer.FirstNamebez konieczności jawnego łączenia, co jest niezwykle wygodne.

Podsumowując, moje pytania to:

  1. Czy są jakieś istotne wady korzystania z niezależnych stowarzyszeń? i...
  2. Jeśli ich nie ma, jaki byłby w ogóle powód używania skojarzeń kluczy obcych?
Daniel Liuzzi
źródło

Odpowiedzi:

106

Jeśli chcesz w pełni wykorzystać ORM, na pewno skorzystasz z referencji Entity:

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Po wygenerowaniu modelu jednostki z bazy danych za pomocą SK zawsze generuje odwołania do jednostek. Jeśli nie chcesz ich używać, musisz ręcznie zmodyfikować plik EDMX i dodać właściwości reprezentujące SK. Tak przynajmniej było w przypadku Entity Framework v1, gdzie dozwolone były tylko niezależne stowarzyszenia.

Entity Framework v4 oferuje nowy typ asocjacji o nazwie Asocjacja klucza obcego. Najbardziej oczywista różnica między przypisaniem klucza niezależnego i obcego występuje w klasie Order:

public class Order
{
    public int ID { get; set; }
    public int CustomerId { get; set; }  // <-- Customer ID
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Jak widać, masz zarówno właściwość FK, jak i odniesienie do jednostki. Istnieje więcej różnic między dwoma typami asocjacji:

Niezależne stowarzyszenie

  • Jest reprezentowany jako oddzielny obiekt w ObjectStateManager. Ma swoje EntityState!
  • Budując stowarzyszenie, zawsze potrzebujesz uprawnień z obu stron stowarzyszenia
  • To powiązanie jest mapowane w taki sam sposób jak jednostka.

Skojarzenie klucza obcego

  • Nie jest reprezentowany jako oddzielny obiekt w programie ObjectStateManager. W związku z tym musisz przestrzegać specjalnych zasad.
  • Budując stowarzyszenie, nie potrzebujesz obu końców skojarzenia. Wystarczy mieć podmiot potomny i PK podmiotu dominującego, ale wartość PK musi być niepowtarzalna. Dlatego podczas korzystania z asocjacji kluczy obcych należy również przypisać tymczasowe unikalne identyfikatory do nowo wygenerowanych jednostek używanych w relacjach.
  • To skojarzenie nie jest mapowane, ale zamiast tego definiuje ograniczenia referencyjne.

Jeśli chcesz użyć skojarzenia klucza obcego, musisz zaznaczyć opcję Uwzględnij kolumny kluczy obcych w modelu w Kreatorze Entity Data Model Wizard.

Edytować:

Zauważyłem, że różnica między tymi dwoma typami skojarzeń nie jest zbyt dobrze znana, więc napisałem krótki artykuł, w którym opisałem to ze szczegółami i własną opinią na ten temat.

Ladislav Mrnka
źródło
Dziękuję za bardzo wnikliwą odpowiedź, a także za ostrzeżenia dotyczące prawidłowej terminologii, co pomogło mi znaleźć wiele zasobów na ten temat oraz zalety / wady obu technik.
Daniel Liuzzi,
1
Właśnie trafiłem na twój artykuł, Ladislav. Bardzo ciekawa lektura i świetne źródło informacji pozwalające lepiej zrozumieć różnicę między tymi dwoma podejściami. Twoje zdrowie.
Daniel Liuzzi
1
@GaussZ: Z tego co wiem, nie było żadnej zmiany w sposobie obsługi stowarzyszeń od czasu EF4 (gdzie zostały wprowadzone stowarzyszenia FK).
Ladislav Mrnka
1
Ta i inne odpowiedzi nie wydają się wpływać na obawy dotyczące wydajności. Jednak zgodnie z sekcją 2.2 Czynniki wpływające na wydajność generowania widoku z artykułu MSDN, użycie niezależnych skojarzeń wydaje się zwiększać koszt generowania widoku w porównaniu z asocjacjami klucza obcego.
Veverke
1
@LadislavMrnka: czy możesz dwukrotnie sprawdzić, czy link do artykułu, o którym wspomniałeś powyżej, działa? Nie mam do niego dostępu.
Veverke,
34

Użyj obu. I spraw, aby twoje encje były wirtualne, aby umożliwić leniwe ładowanie. Lubię to:

public class Order
{
  public int ID { get; set; }
  public int CustomerID { get; set; }
  public virtual Customer Customer { get; set; } // <-- Customer object
  ...
}

Oszczędza to niepotrzebne wyszukiwania bazy danych, pozwala na leniwe ładowanie i pozwala łatwo zobaczyć / ustawić identyfikator, jeśli wiesz, jaki ma być. Zauważ, że posiadanie obu nie zmienia w żaden sposób struktury tabeli.

Seth Paulson
źródło
5
Zgoda. Tak właśnie zrobiłem, jak zasugerował Ladislav. To naprawdę daje ci to, co najlepsze z obu światów; cały obiekt, gdy potrzebujesz wszystkich jego właściwości, a jego identyfikator, gdy potrzebujesz tylko PK i nie dbasz o resztę.
Daniel Liuzzi
9

Niezależne skojarzenie nie działa dobrze z AddOrUpdatetym, co jest zwykle używane w Seedmetodzie. Gdy odniesienie jest istniejącym elementem, zostanie ponownie wstawione.

// Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, Customer = customer };
db.Set<Order>().AddOrUpdate(order);

W rezultacie istniejący klient zostanie ponownie wstawiony, a nowy (ponownie wstawiony) klient zostanie powiązany z nowym zamówieniem.


Chyba że użyjemy skojarzenia klucza obcego i przypiszemy identyfikator.

 // Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, CustomerId = customer.Id };
db.Set<Order>().AddOrUpdate(order);

Mamy oczekiwane zachowanie, dotychczasowy klient zostanie powiązany z nowym zamówieniem.

Yuliam Chandra
źródło
2
To jest dobre odkrycie. Jednak obejściem (i myślę, że właściwym sposobem) dołączenia klienta do zamówienia jest załadowanie go z kontekstu db w następujący sposób: var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; Lub możesz użyć metody Select, aby załadować klienta z kontekstu db. Działa to z niezależnym stowarzyszeniem.
tala9999
4

Preferuję podejście obiektowe, aby uniknąć niepotrzebnych wyszukiwań. Obiekty właściwości można równie łatwo wypełnić, wywołując metodę fabryki w celu zbudowania całej jednostki (przy użyciu prostego kodu wywołania zwrotnego dla jednostek zagnieżdżonych). Nie ma żadnych wad, które widzę poza zużyciem pamięci (ale buforowałbyś swoje obiekty, prawda?). Zatem wszystko, co robisz, to zastępowanie stosu stosem i zwiększanie wydajności wynikające z nieprzeprowadzania wyszukiwań. Mam nadzieję, że to ma sens.

CarneyCode
źródło