Trudny / egzaminacyjny · lekcja 17
Relacje między tabelami w EF Core — klucze obce i Include
Prawdziwe aplikacje niemal zawsze mają dane POWIĄZANE ze sobą — produkt należy do kategorii, zamówienie należy do klienta i zawiera wiele produktów. Ta lekcja pokazuje, jak Entity Framework Core reprezentuje takie relacje w klasach C# (bez ręcznego pisania SQL...
Zacznij tutaj
Po co się tego uczymy?
Wprowadzenie
Teoria
Relacja jeden-do-wielu (one-to-many) to najczęstszy typ powiązania: JEDNA kategoria ma WIELE produktów, ale każdy produkt należy do DOKŁADNIE JEDNEJ kategorii. W EF Core reprezentuje się to przez:
- Właściwość klucza obcego:
public int KategoriaId { get; set; }w klasieProdukt— odpowiednik kolumnykategoria_idw tabeli SQL; - Właściwość nawigacyjna:
public Kategoria Kategoria { get; set; }w klasieProdukt— pozwala w kodzie C# odwołać się bezpośrednio do CAŁEGO obiektu kategorii (produkt.Kategoria.Nazwa), zamiast tylko jej Id; - Właściwość nawigacyjna kolekcji (opcjonalnie, po "drugiej stronie"):
public List<Produkt> Produkty { get; set; }w klasieKategoria— pozwala pobrać WSZYSTKIE produkty danej kategorii przezkategoria.Produkty.
Leniwe ładowanie a Include. Domyślnie, gdy pobierasz produkty (context.Produkty.ToListAsync()), EF Core NIE ładuje automatycznie powiązanej kategorii — właściwość produkt.Kategoria pozostałaby null. Żeby EF Core dołączył powiązane dane w JEDNYM zapytaniu (SQL JOIN pod spodem), trzeba jawnie użyć metody Include: context.Produkty.Include(p => p.Kategoria).ToListAsync() — dopiero wtedy produkt.Kategoria jest faktycznie wypełnione.
Dlaczego EF Core nie ładuje wszystkiego automatycznie? Wydajność — gdyby KAŻDE zapytanie automatycznie dociągało WSZYSTKIE powiązane dane (kategorię, a może kategoria ma powiązanego dostawcę, a dostawca adres...), pojedyncze proste zapytanie mogłoby niepotrzebnie pobierać ogromne ilości danych. Include pozwala programiście świadomie zdecydować, JAKIE powiązane dane są faktycznie potrzebne w danym miejscu kodu.
Konfiguracja relacji w DbContext. W najprostszych przypadkach EF Core SAM rozpoznaje relację po nazwach właściwości (konwencja: KategoriaId + właściwość nawigacyjna Kategoria) — nie trzeba nic dodatkowo konfigurować. W bardziej złożonych przypadkach (np. gdy nazwy nie pasują do konwencji) używa się OnModelCreating w DbContext z metodami modelBuilder.Entity<Produkt>().HasOne(p => p.Kategoria).WithMany(k => k.Produkty).HasForeignKey(p => p.KategoriaId).
Relacja wiele-do-wielu (many-to-many) — rzadsza, ale ważna (np. zamówienie może zawierać wiele produktów, a każdy produkt może być w wielu zamówieniach) — wymaga DODATKOWEJ tabeli pośredniczącej (np. PozycjaZamowienia z ZamowienieId, ProduktId i dodatkowo Ilosc) — dokładnie ta sama koncepcja klucza obcego/tabeli łączącej, którą poznałeś w dziale "Bazy danych i SQL" i w projekcie Produkty+Koszyk z działu MAUI.
Zobacz zależności
Schemat
Tabela Kategorie Tabela Produkty
┌──────────────┐ ┌──────────────────┐
│ Id (PK) │◄───────────────────┤ KategoriaId (FK) │
│ Nazwa │ jeden-do-wielu │ Id (PK) │
└──────────────┘ │ Nazwa │
│ Cena │
└──────────────────┘
Klasy C# (właściwości nawigacyjne):
class Kategoria { ... List<Produkt> Produkty { get; set; } }
class Produkt { ... int KategoriaId; Kategoria Kategoria { get; set; } }
context.Produkty.Include(p => p.Kategoria).ToListAsync()
│
▼
SQL (generowany automatycznie przez EF Core):
SELECT p.*, k.* FROM Produkty p
JOIN Kategorie k ON p.KategoriaId = k.Id
│
▼
produkt.Kategoria.Nazwa ← teraz dostępne w C#, bez dodatkowego zapytania
Połącz z praktyką
Przykład z życia
Sklep internetowy z kategoriami "Elektronika", "Akcesoria", "Odzież" — każdy produkt należy do JEDNEJ kategorii, a strona kategorii pokazuje WSZYSTKIE należące do niej produkty. To dokładnie ta relacja jeden-do-wielu, którą poznajesz w tej lekcji — identyczna koncepcyjnie do relacji Produkty-Koszyk z projektu w dziale MAUI, tylko realizowana przez EF Core zamiast ręcznego SQL JOIN.