2010-07-02 23 views
8

Proszę pomóż projektowi EF n00b w jego bazie danych. Mam kilka firm, które produkują kilka produktów, więc istnieje wiele do wielu relacji między firmami i produktami. Mam tabelę pośrednią, Company_Product, która je łączy.Entity Framework pytanie "wiele-do-wielu"

Każda kombinacja firmy/produktu ma unikalny kod SKU. Na przykład widżety Acme mają kod SKU 123, ale widżety Omega mają kod SKU 456. Dodałem kod SKU jako pole w tabeli pośredniej Company_Product.

EF wygenerował model z relacją 1: * między firmą a Company_Product tables oraz relacją 1: * między produktem a Company_Product tables. Naprawdę chcę mieć związek między firmą a produktem. Ale, co najważniejsze, nie ma sposobu, aby uzyskać dostęp do SKU bezpośrednio z modelu.

Czy muszę umieścić SKU w swoim własnym stole i napisać połączenie, czy jest lepszy sposób?

+0

Wszystko wygląda dobrze ... Powinieneś mieć 3 elementy w swoim modelu: Firma, Produkt i Company_Product. "Nie ma sposobu uzyskania dostępu do SKU bezpośrednio z modelu" wypróbowałeś ctx.Company_Products.First(). SKU? –

Odpowiedz

33

Właśnie przetestowane w projekcie nowego VS2010 (EFv4), aby upewnić się, i oto co znalazłem:

Gdy tabela asocjacyjna w środku (Company_Product) ma tylko 2 kluczy obcych do innych tabel (CompanyID i ProductID), a następnie dodanie wszystkich 3 tabel do projektanta kończy modelowanie wielu do wielu relacji. Nie generuje nawet klasy dla tabeli Company_Product. Każda firma ma kolekcję Produktów, a każdy Produkt ma kolekcję Firm.

Jeśli jednak Twoja tablica asocjacyjna (Company_Product) ma inne pola (takie jak SKU, własny klucz podstawowy lub inne opisowe pola, takie jak daty, opisy itp.), Wówczas modelarz EF utworzy oddzielną klasę, a robi to, co już widziałeś.

Posiadanie klasy w środku z relacjami 1: * do firmy i produktu nie jest złą rzeczą, a wciąż możesz uzyskać żądane dane za pomocą łatwych zapytań.

// Get all products for Company with ID = 1 
var q = 
    from compProd in context.Company_Product 
    where compProd.CompanyID == 1 
    select compProd.Product; 

To prawda, że ​​nie jest tak łatwo poruszać się po prostu relacje modelu, kiedy masz już załadowana jednostka obiektów, na przykład, ale to, co jest dla warstwy danych. Uwzględnij zapytania, które uzyskają pożądane dane. Jeśli naprawdę chcesz pozbyć się tej średniej klasy Company_Product i mieć wiele-do-wielu bezpośrednio reprezentowanych w modelu klasy, to musisz rozebrać tabelę Company_Product tak, aby zawierał tylko 2 klucze zagraniczne i pozbyć się ich. SKU.

Właściwie nie powinienem mówić, że MUSISZ to zrobić ... Możliwe, że będziesz w stanie dokonać pewnych zmian w projektancie i tak skonfigurować to w ten sposób. Spróbuję i zgłoś się.

UPDATE

Utrzymanie SKU w tabeli Company_Product (czyli mój model EF miał 3 klasy, a nie 2; stworzył klasę Company_Payload, z 1: * dla pozostałych 2 stoły), próbowałem aby dodać powiązanie bezpośrednio między firmą a produktem.Etapy I obserwowani byli:

  • prawym przyciskiem myszy na klasy Spółki w projektancie
  • Dodaj> Stowarzyszenie
  • Ustaw „End” na lewo, aby być firmą (powinien być już)
  • Set „Koniec” w sprawie prawa do produktu
  • zmienić zarówno krotności na „* (wiele)”
  • właściwości nawigacyjne powinny być nazywane „produkty” i „Spółkami”
  • wciśnij OK.
  • prawym przyciskiem myszy na połączeniu w modelu> kliknij „tablica odwzorowań”
  • W sekcji „Dodaj tabelę lub widok” wybrać „Company_Product”
  • Mapa Firma -> ID (po lewej) do CompanyID (po prawej)
  • Mapa Produkty -> ID (po lewej) do IDProduktu (po prawej)

Ale to nie działa. Daje ten błąd: Błąd 3025: Problem z odwzorowaniem fragmentów rozpoczynających się od wiersza 175: Należy określić mapowanie dla wszystkich kluczowych właściwości (Company_Product.SKU) tabeli Company_Product.

To konkretne powiązanie jest nieprawidłowe, ponieważ używa Company_Product jako tabeli, ale nie mapuje pola SKU na nic.

Ponadto, podczas badania tego, natrafiłem na tę "najlepszą praktykę" z książki Entity Framework 4.0 Recipies (zauważ, że dla tabeli asocjacyjnej z dodatkowymi polami, oprócz 2 FK, odnoszą się one do dodatkowych pól jako "ładunek" W twoim przypadku SKU jest ładunkiem w Company_Product).

Best Practice

Unfortunately, a project that starts out with several, payload-free, many-to-many relationships often ends up with several, payload-rich, many-to-many relationships. Refactoring a model, especially late in the development cycle, to accommodate payloads in the many-to-many relationships can be tedious. Not only are additional entities introduced, but the queries and navigation patterns through the relationships change as well. Some developers argue that every many-to-many relationship should start off with some payload, typically a synthetic key, so the inevitable addition of more payload has significantly less impact on the project.

So here's the best practice. If you have a payload-free, many-to-many relationship and you think there is some chance that it may change over time to include a payload, start with an extra identity column in the link table. When you import the tables into your model, you will get two one-to-many relationships, which means the code you write and the model you have will be ready for any number of additional payload columns that come along as the project matures. The cost of an additional integer identity column is usually a pretty small price to pay to keep the model more flexible.

(Z rozdziału 2. Podmiotowi modelowania danych, 2.4. Podstawy modelowania wiele do wielu relacji z Payload)

Brzmi jak dobra rada. Zwłaszcza, że ​​masz już ładunek (SKU).

+0

Uruchamiany w tym samym, dokładnym numerze, dobra odpowiedź. –

2

Chciałbym tylko dodać następujące do Samuela odpowiedź:

Jeśli chcesz bezpośrednio kwerendy z jednej strony wiele-do-wielu relacji (z ładunkiem) do drugiego, można użyć następujących Kod (przy użyciu tego samego przykładu):

Company c = context.Companies.First(); 
IQueryable<Product> products = c.Company_Products.Select(cp => cp.Product); 

zmienna products będzie wówczas wszystkie Product zapisy związane z rekordem Company c. Jeśli chciałbyś dołączyć SKU dla każdego z produktów, można użyć anonimowego klasy tak:

var productsWithSKU = c.Company_Products.Select(cp => new { 
    ProductID = cp.Product.ID, 
    Name = cp.Product.Name, 
    Price = cp.Product.Price, 
    SKU = cp.SKU 
}); 
foreach (var 

można upakować pierwsze zapytanie w nieruchomości tylko do odczytu dla uproszczenia tak:

public partial class Company 
{ 
    public property IQueryable<Product> Products 
    { 
    get { return Company_Products.Select(cp => cp.Product); } 
    } 
} 

Nie można tego zrobić w zapytaniu zawierającym kod SKU, ponieważ nie można zwrócić typów anonimowych. Konieczne byłoby posiadanie określonej klasy, którą zwykle można uzyskać, dodając nieprzypisaną właściwość do klasy Product lub tworząc inną klasę dziedziczącą po Product, która dodaje właściwość SKU.Jeśli jednak korzystasz z dziedziczonej klasy, nie będziesz w stanie wprowadzać w niej zmian i zarządzać nią EF - przydałaby się tylko do celów wyświetlania.

Pozdrawiam. :)