Entity Framework w ujęciu Code First

W poprzednim poście pokazałem jeden ze sposobów na przetestowanie kodu korzystającego z bazy danych. Dziś chciałbym napisać kilka zdań o tym jak w prosty sposób za pomocą klas opisujących modele danych i kontekst bazy danych możemy utworzyć (i synchronizować w razie zmian) rzeczywistą bazę danych, z której docelowo korzystać będzie aplikacja. Entity Framework daje do dyspozycji programisty trzy podejścia współpracy z fizyczną bazą danych:

  • Code First
  • Model First
  • Database First

Do tej pory korzystałem jedynie z podejścia Database First, czyli utworzenia bazy danych poza środowiskiem Visual Studio, a następnie jej importu do modeli Entity Framework. Taki schemat postępowania sprawdzał się całkiem dobrze, czasem jedynie sprawiał niespodzianki np. nie aktualizując zmienionego typu danych kolumny tabeli, lub pozostawiając w modelu usuniętą kolumnę. Tym razem chcę więc zobaczyć jak będzie wyglądała praca w podejściu Code First – najpierw w Visual Studio tworzymy kod modeli danych, ich właściwości i powiązań, a następnie tak przygotowany schemat migrujemy do bazy danych. Taki wariant wydaje się być wygodniejszy i naturalniejszy dla programisty, a dodatkowo łatwiejsze wydaje się zarządzanie późniejszymi zmianami struktury bazy danych.

Od czego zatem zaczniemy?

Od utworzenia modeli danych, które mają być odzwierciedlone w formę tabel w bazie danych. Przygotowałem zatem kilka klas opisujących podstawowe modele danych – wokół centralnego obiektu Movie. Diagram pokazujący zależności między modelami wygląda tak:

006a

a kod samych klas jak poniżej

public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public int ProductionYear { get; set; }

        public virtual ICollection<MovieGenre> MovieGenres { get; set; }
        public virtual ICollection<MovieRating> MovieRatings { get; set; }
    }

public class Genre
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public virtual ICollection<MovieGenre> MovieGenres { get; set; }
    }

public class RatingSite
    {
        public int ID { get; set; }
        public string Url { get; set; }
        public string Name { get; set; }

        public virtual ICollection<MovieRating> MovieRatings { get; set; } 
    }
public class MovieGenre
    {
        public int ID { get; set; }
        public int MovieID { get; set; }
        public int GenreID { get; set; }

        public virtual Movie Movie { get; set; }
        public virtual Genre Genre { get; set; }
    }

public class MovieRating
    {
        public int ID { get; set; }
        public int MovieID { get; set; }
        public int RatingSiteID { get; set; }
        public Nullable<decimal> Rating { get; set; }

        public virtual Movie Movie { get; set; }
        public virtual RatingSite RatingSite { get; set; }
    }

Oprócz prostej definicji właściwości, które w danym modelu chcemy mieć do dyspozycji, w klasach deklarujemy również relacje między modelami: np. model Movie jest w relacji jeden-do-wielu z modelem MovieGenre (jeden film może należeć do kilku gatunków), dlatego model Movie posiada właściwość public virtual ICollection<MovieGenre> MovieGenres, a model MovieGenre posiada właściwość public virtual Movie Movie. Właściwości o typie ICollection<KlasaModeluDanych> określają relację jeden-do-wielu (lub wiele-do-wielu), a właściwości o typie KlasaModeluDanych określają klucz obcy dla modelu – oba te typy są określane jako navigation properties. Co daje modyfikator virtual w definicji tych właściwości? Entity Framework będzie korzystał z mechanizmu lazy loading, a więc nie będzie wczytywał listy gatunków, do których należy film do momentu gdy się do niej nie odwołamy.

Mamy więc przygotowane klasy z modelami, potrzebna nam jest jeszcze klasa określająca kontekst bazy danych – musi dziedziczyć po klasie DbContext. Poniżej przykład takiej klasy – łączącej zdefiniowane wyżej modele danych:

public AppContext(): base("VodSearcher")
{ }

public virtual DbSet<Movie> Movies { get; set; }
public virtual DbSet<Genre> Genres { get; set; }
public virtual DbSet<RatingSite> RatingSites { get; set; }
public virtual DbSet<MovieGenre> MovieGenres { get; set; }
public virtual DbSet<MovieRating> MovieRatings { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}

 

 

Możemy tutaj dodatkowo przeciążyć metodę OnModelCreating i określić dodatkowe zasady, według których tworzona będzie struktura bazy danych. W przykładzie określamy, że chcemy by nazwy tabel w bazie danych były w liczbie pojedyńczej (np. Movie zamiast Movies).

Brakuje nam teraz jeszcze tylko definicji połączenia z bazą danych, tak by mechanizm migracji Entity Framework wiedział, jakiego silnika baz danych użyć i gdzie bazę utworzyć. Razem z Visual Studio instalowany jest silnik LocalDb – bazy danych operującej na plikach *.mdf. Definicja połączenia z taką bazą wygląda następująco:

<connectionStrings>
<add name="VodSearcher" connectionString="data source=(LocalDb)\MSSQLLocalDB;initial catalog=VodSearcher;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>

i siedzi w pliku App.config.

Uff! Mamy już wszystko, co potrzebne jest do migracji modelu danych do fizycznej bazy. Jak to zrobić?

1. Z menu wybieramy opcję Tools->NuGet Packet Manager->Package Manager Console

2. Upewniając się, że jesteśmy w odpowiednim projekcie (u nas VodSearcher.Data) wywołujemy polecenie Enable-Migration. Spowoduje to utworzenie katalogu Migrations oraz klasy Configuration, w której np. możemy określić wstępne zasilenie bazy danymi.

006c

3. Dodajemy pierwszą migrację poleceniem Add-Migration NazwaMigracji – spowoduje utworzenie w katalogu Migrations klasy o nazwie NazwaMigracji dziedziczącej po klasie DbMigration i zawierającej w metodzie Up() listę operacji do wykonania w bazie, a w metodzie Down() listę operacji do wykonania przy wycofaniu migracji.

006d

4. Ostatnią operacją, która spowoduje utworzenie/zaktualizowanie bazy danych jest wywołanie polecenia Update-Database.

006e

I już – wystarczy teraz wybrać opcję View->Sql Server Object Explorer by zobaczyć nowo utworzoną bazę danych:

006f

W przypadku zmian istniejących lub dodawania nowych modeli wystarczy dodać nową migrację (Add-Migration) i zaktualizować bazę danych (Update-Database). That simple! Przyznam, że podoba mi się podejście Code First – czuję, że mam kontrolę nad strukturą bazy danych i nie muszę się obawiać utraty spójności między nią, a kodem warstwy DAL w aplikacji.

 

Marcin

Dodaj komentarz