EF: Relacje między tabelami – Code-First

ORM taki jak Entity Framework, mocno ułatwia nam pracę w kodzie z bazą danych (goodbye ADO.NET 🙂 ), ale nie rozwiązuje za nas problemu zaprojektowania takiej bazy. Jedną z podstawowych kwestii przed jakimi stajemy jest odpowiedź na pytanie o sposób łączenia tabel. W tym poście nie będę pisał jednak o projektowaniu baz danych, (przy założeniu, że masz pomysł na swoją bazę ;)) a o sposobie implementacji znanych wszystkim relacji w podejściu Code-First.

Na początek przypomnijmy sobie jakimi, w relacyjnych bazach danych, relacjami (a jakże :)) mogą być związane tabele.

Jeden do jednego

Jednemu wierszowi w pierwszej tabeli, odpowiada jeden i tylko jeden wiersz z drugiej tabeli.

Jeden do wielu

Jednemu wierszowi w pierwszej tabeli, może odpowiadać wiele wierszy z drugiej tabeli.

Wiele do wielu

Jak sama nazwa wskazuje :). Taką tabelę zwykle najkorzystniej jest podzielić, przy użyciu tzw. Tabeli łączącej na relację jeden do wielu.

Teraz zobaczmy jak zaimplementować te relacje w EF w podejściu Code-First:

Relacja jeden do jednego:

Załóżmy, że chcemy użyć tej relacji do połączenia podstawowych danych ucznia i jego średniej. Tabele nich nazywają się Student i AverageGrade. Jeden uczeń może mieć tylko jedną średnią ocen :).

W EF kolumny tabel są reprezentowane przez właściwości. Nasze klasy reprezentujące tabele i mające być połączone relacją jeden do jeden, powinny oprócz właściwych sobie kolumn zawierać wirtualną składową właściwość, reprezentującą powiązaną tabelę. Czyli w klasie Student powinna być:

public virtual AverageGrade AverageGrade {get; set;}

a w klasie AverageGrade:

public virtual Student Student {get; set;}

W uproszczeniu i pomijając ficzery takie jak DataAnnotations, klasy mogłyby wyglądać w ten sposób:

public class Student
{
    public int Id {get; set;}
    public string Name {get; set;}

    public virtual AverageGrade AverageGrade {get; set;}
}

public class AverageGrade
{
    public int Id {get; set;}
    public float value {get; set;}

    public virtual Student Student {get; set;}
}

 

Relacja jeden do wielu:

Teraz zmieńmy sobie kontekst :). Naszymi tabelami niech będą Employer i Worker. Jeden pracodawca może zatrudniać wielu pracowników, ale jeden pracownik ma tylko jednego pracodawcę.

W takim wypadku klasa reprezentująca tabelę z pracownikami powinna zawierać, oprócz swoich zwykłych właściwości, publiczną wirtualną właściwość pozwalającą na przypisanie do niej implementacji interfejsu generycznego ICollection<T>  gdzie T to typ przechowywanych elementów, w tym wypadku Worker:

public virtual ICollection<Worker> Workers{get; private set;}

Tą kolekcję powinno się zainicjalizować w konstruktorze naszej klasy. Worker natomiast powinien, tak jak w przypadku relacji jeden do jeden, posiadać odwołanie do klasy Employer:

public virtual Employer Employer {get; private set;}

Obie klasy w uproszczeniu mogłyby wyglądać tak:

public class Employer
{
    public Employer()
    {
        this.Workers = new HashSet<Worker>(); 
    }

    public int Id {get; set;}
    public decimal NominalCapital {get; set;}
    public string CompanyName {get; set;}

    public virtual ICollection<Worker> Workers {get; set;}
}

public class Woker
{
    public int Id {get; set;}
    public string Name {get; set;}

    public virtual Employer Employer {get; set;}
}

Relacja wiele do wielu:

Na koniec została nam powszechna, choć niewdzięczna, relacja do właściwego zastosowania której, zwykle używamy trzech tabel. Dwóch głównych i jednej tzw. tabeli łączącej. Tabela łącząca służy do rozbicia relacji, na dwie jeden do wielu. W Entity Framework tabela łącząca jest tworzona automatycznie, o ile poprawnie zaimplementujemy klasy mające odzwierciedlać relację wiele do wielu. Przyjrzyjmy się tabelom: TrainginDay i Exercise. Jeden dzień treningowy, może mieć wiele ćwiczeń, tak samo jak jedno ćwiczenie może być przypisane do wielu dni treningowych. Obie klasy reprezentujące te tabele, powinny zawierać referencje do ICollection<T> (jak w jeden do wielu). Co do utworzenia tych kolekcji, to wystarczy, że zainicjujemy tylko jedną z nich.

Prosta implementacja tych klas mogłaby wyglądać tak:

public class Exercise
{
    public Exercise()
    {
        this.TrainingDays = new HashSet<TrainingDay>(); 
    }

    public int Id {get; set;}
    public string Name {get; set;}
    public string Description {get; set;}

    public virtual ICollection<TrainingDay> TrainingDays{get; set;}
}

public class TrainingDay
{
    public int Id {get; set;}
    public int IdOfPlan {get; set;}
    public int IdOfDayOfWeek {get; set;}

    public virtual ICollection<Exercise> Exercises{get; set;}
} 

EF utworzy za nas tabelę łączącą TrainingDayExcersise zawierającą tylko klucze główne do obu tych tabel.

Jeżeli chodzi o relacje między tabelami w Entity Framework, to chyba udało mi się zawrzeć tu to co jest najważniejsze. W bliskiej przyszłości napiszę co nieco o czymś co nazywa się DataAnnotations. W praktyce są to atrybuty, pozwalające nakładać na nasze kolumny min. ograniczenia, czy określać klucze.

2 myśli na temat “EF: Relacje między tabelami – Code-First

  1. Siemka,
    mógłbyś mi wyjaśnić parę rzeczy z EntityFramework i podejścia CodeFirst w C#?. Jak to wygląda z relacjami?? z tego co w netach czytałem to jeśli chcę jeden-do-jednego to robię w tabeli coś w stylu

    public class Player
    {
    public int Id {get; set; } // tutaj niby już automatycznie ustawia jako klucz główny tabeli, inny sposób co widziałem to podanie tej właściwości z inną nazwą i dodanie atrybutu nad nim [Key] ??

    public string Nick { get; set;}
    public virtual Guild Guild { get; set; }

    // coś tam dalej
    }

    public class Guild
    {
    public int Id { get; set; }
    public string Name { get; set;}

    public virtual ICollection Players { get; set; }

    public Guild()
    {
    this.Players = new HashSet();
    }
    }

    I u góry podana nazwa właściwości relacji identycznie co nazwa klasy ‘public virtual Employer Employer {get; set;}’ i tutaj pytanie czy zawsze tak musi być?? (w sumie mógłbym sam sprawdzić, ale wolę dopytać kogoś ogarniętego w temacie).

    Podobnie wygląda przy relacji jeden do wielu. I nie wiem czy tutaj taki wymóg, gdzie później to rozpoznaje jako relację, że podaje się liczbę mnogą do klasy, z angielskiego to pewnie z automatu typu Player to Players, Guild i Guilds. A jak nazwy będą Polskie?? np. Gracz to jak wpiszę Gracze to będzie ok?? jak to wygląda z tymi nazwami poprawnymi, żeby widziało jako normalne relacje.

    Kolejne pytanie to jak to z odczytaniem wygląda żeby od razu mi odczytało całe relacje bez Lazy-loading czy jak to tam się zwało, że zaczytywało by z czasem. Dawniej coś z tym kombinowałem no i używałem Include np. w taki sposób:

    var guilds = context.Guilds
    .Include(“Players”)
    .ToList();

    Tylko pytanie czy w taki sposób poprawnie się pobiera dane jeśli relacje utworzone?

    No i ostatnie pytanie czy jest jakaś różnica przy nazwach tych DbSetów w klasie DbContextu np

    public DbSet Gracze {get; set;}

    czy tutaj jest jakaś różnica w tym czy nazwę Gracze czy też GraczeWypasni itd.??

    Z góry wielkie dzięki za wyjaśnienia :). Bo tak to kurcze wiem tak pół na pół, a chciałbym się upewnić jak to dokładnie wygląda 🙂

    PS. Jak coś to w necie trochę czytałem, tylko akurat tego typu odpowiedzi nie uzyskałem, a przykłady jakie pokazywali to właśnie już z podanymi nazwami coś na styl powyższych.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *