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?

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 JOIN) — dokładnie ten sam problem, który w dziale MAUI rozwiązywałeś ręcznym zapytaniem JOIN w Microsoft.Data.Sqlite, tutaj EF Core robi to automatycznie.

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 klasie Produkt — odpowiednik kolumny kategoria_id w tabeli SQL;
  • Właściwość nawigacyjna: public Kategoria Kategoria { get; set; } w klasie Produkt — 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 klasie Kategoria — pozwala pobrać WSZYSTKIE produkty danej kategorii przez kategoria.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.