Migracja schematu bazy danych w Entity Framework

Wprowadzenie

W obecnych czasach bardzo rzadko tworzy się aplikacje, które nie korzystają z bazy danych. Gdzieś trzeba zapisać dane. Od jakiegoś czasu coraz bardziej popularne stają są bazy nierelacyjne, ale dalej najczęściej używamy baz relacyjnych, takich jak na przykład SQL Server. Jednym z problemów z jaki musimy się zmierzyć jest zmiana schematu bazy danych na przestrzeni czasu. A to musimy dodać miejsce na nowe dane. Usunąć już istniejące. Czy też zmienić strukturę, aby zapytania wykonywały się szybciej.

Pracując z różnymi firmami, zespołami, czy to pojedynczymi developerami, zauważyłem, że jest to jeden z problemów, którego najczęściej programiści nie umieją dobrze rozwiązać. A to w repozytorium znajdują się dziesiątki skryptów sql, które tworzą schemat bazy i tylko niektóre osoby wiedzą w jaki sposób je wykonać, aby baza działała poprawnie. A to każda wdrożona baza ma trochę inną strukturę, gdzie później doprowadzenie wszystkiego do ładu zajmuje wiele cennych godzin. A to … zapewne sam masz w pamięci jakąś nieciekawą sytuację, a może nawet na co dzień musisz z tym walczyć.

Dlatego postanowiłem przygotować trzy wpisy na temat różnych bibliotek, które możesz wykorzystać do zarządzania schematem bazy danych, aby ta kwestia przestała być problem. Dzisiaj na pierwszy rzut pójdą migracje w Entity Framework. A w kolejnych dwóch wpisach pokażę Ci inne biblioteki, które rozwiązują ten problem w trochę inny sposób. Dzięki czemu, będziesz mógł/mogła wybrać to co najlepiej spełni Twoje potrzeby.

Migracje w Entity Framework

Entity Framework od dłuższego czasu wspiera migracje schematu bazy danych w bardzo przyjemny sposób. Migracje są generowane w sposób automatyczny lub ręczny na podstawie zmian w modelu obiektowym aplikacji. Dzięki czemu nie musimy w większości przypadków o ogóle dotykać sqla, ale jak zobaczysz później, nie zawsze jesteśmy w stanie z niego zrezygnować.

Najfajniejsze jest to, że Entity Framework pilnuje uruchamiania migracji na bazie. W specjalnej tabeli zapisuje informacje, które migracje się wykonały i nie musimy ręcznie tego pilnować.

Na ogół tworzymy dedykowanych projekt w solution, w którym wrzucamy cały kod związany z warstwą dostępu do danych. W tym projekcie znajduje się obiekt kontekstowy z Entity Framework oraz później z czasem migracje. Aby włączyć mechanizm migracji w tym projekcie wykonujemy komendę „enable-migrations” w konsoli Nugeta (wybieramy projekt warstwy dostępu do danych w konsoli, aby nie trzeba było tego określać w wywołaniu komendy):

entity framework enable migrations

Po wykonaniu tej komendy w projekcie pojawi się nowy katalog (Migrations), w którym na początku znajduje się jedna klasa (Configuration), a z czasem będą pojawiać się nowe klasy z migracjami (zrzut ekranu poniżej pokazuje testowych projekt z dodanymi migracjami):

entity framework solution with migrations

Tutaj jeszcze mała uwaga: komendy Entity Framework wykonywane w konsoli Nugeta są wykonywane na aktualnie wybranych projekcie. Dodatkowo część komend potrzebuje connection stringa do bazy. Wartość jest brana z konfiguracji projektu, który jest ustawiony jako projekt startowy (pogrubiona nazwa w Solution Explorer – w powyższym rzucie ekranu jest to EFMigrationExample.Migrator). Warto o tym pamiętać, w momencie, gdy dostaniesz błąd o tym, że nie udało się znaleźć connection stringa.

Dodanie migracji

Dodanie nowej migracji w Entity Framework nie jest trudne. Wystarczy na początku dodać nową klasę modelu do aplikacji lub zmienić już istniejącą. W przykładzie w pierwszej kolejności dodałem klasę Product, która wygląda tak:

Klasa składa się z trzech prostych właściwości. Id dla klucza głównego w tabeli. Name – nazwy produktu. Category – nazwa kategorii. Dodanie nowej migracji sprowadza się do wywołania w konsoli Nugeta komendy „add-migration AddProduct”. Gdzie AddProduct to nazwa migracji. Ostateczna migracja wygląda tak:

Jak widać w migracji mamy dwie metody. Up podnosi wersję bazy danych. Down cofa zmiany. Większość kodu została wygenerowana przez Entity Framework. Sam ręcznie dodałem Inserty, aby mieć jakieś dane testowe, które posłużą mi w testowaniu kolejnej migracji.

Aby wykonać migrację możemy używać komendy „update-database” w konsoli Nugeta. Warto dodać jeszcze przełącznik „-verbose”, który powoduje, że w konsoli wyświetli się dużo więcej informacji podczas wykonywania aktualizacji bazy danych. Między innymi zostaną wyświetlone wszystkie wykonane sql. Później jeszcze wrócimy do tego jak można wykonywać w inny sposób migracje.

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 20 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

Dodanie kolejnej migracji

Aby zobaczyć w praktyce migracje warto zmienić trochę model danych i wygenerować kolejną migrację. Na potrzeby przykładu załóżmy, że chcemy zapisywać inaczej informacje o kategorii produktu. Zamiast właściwości typu string w produkcie, chcemy teraz przechowywać te informacje w dedykowanej tabeli. Po zmianie model danych wygląda tak:

Możemy teraz wygenerować kolejną migrację „add-migration AddCategory.cs”, która wygląda tak:

Podobnie jak w poprzedniej migracji, większość kodu zostało wygenerowane przez Entity Framework. Sam ręcznie dodałem dwa sql w metodzie Up oraz jeden w metodzie Down. Dodatkowe sql są odpowiedzialne za przeniesie danych z kolumny Category do nowej tabeli oraz ustawienie odpowiednich CategoryId. W przypadku metody Down po prostu kopiuje z powrotem dane z tabeli do kolumny.

Tym przykładem chciałem pokazać, że bardzo często podczas zmiany schematu bazy danych, musimy również zadbać o zmianę danych. Tak jak to jest w przykładzie, druga migracja kopiuje dane z jednej tabeli do drugiej, abyśmy w aplikacji nie stracili danych.

Ten przykład również pokazuje, że automatyczne migracje dostępne w Entity Framework na dłuższą metę są problematyczne. Dlatego za każdym razem zalecam, aby z nich w ogóle nie korzystać. Zacznij od razu generować migracje w formie kodu. Wtedy masz dużo większą elastyczność i możesz wykonywać dodatkowe rzeczy w samym sql.

Aby przetestować migrację wykonujemy ponownie „update-database” w konsoli Nugeta.

Uruchamianie migracji

Migracje możemy wykonywać w różny sposób na bazie danych. Pokazany wyżej sposób wykorzystujący konsolę Nugeta jest dobry podczas programowania. Ale już w przypadku środowisk testowych i produkcyjnych jest problematyczny.

Jednym z sposób jest skonfigurowanie Entity Framework w ten sposób, aby automatycznie wykonywały migrację podczas pierwszego zapytania w aplikacji. Entity Framework za każdym razem na początku sprawdza aktualny schemat bazy (informacje w tabeli __MigrationHistory, a nie fizyczny schemat bazy) i porównuje go z tym co ma w modelu w aplikacji. Gdy schemat się różni może automatycznie wykonać brakujące migracje. Aby to włączyć wystarczy w web.config lub app.config dodać odpowiednią konfigurację database initializera:

Takie rozwiązanie jest bardzo wygodne, ale też niesie problemy. Największym problemem jest to, że użytkownik, z którego korzystamy w połączeniu do bazy danych musi mieć zwiększone uprawnienia, aby mógł zmienić schemat bazy danych. Co niestety nie jest najlepszym rozwiązaniem, szczególnie, gdy w aplikacji mamy podatność typu sql injection.

Innym rozwiązaniem jest skorzystanie z dedykowanego narzędzia dostarczonego wraz w Entity Framework. Mowa o migrate.exe (https://docs.microsoft.com/pl-pl/ef/ef6/modeling/code-first/migrations/migrate-exe). Narzędzie te można znaleźć w katalogu „packages\EntityFramework.6.2.0\tools”:

entity framework migration exe

Możemy skorzystam z tego narzędzia np. podczas buildu aplikacji. Wykonujemy wtedy migracje z wykorzystując zupełnie innego użytkownika (z odpowiednimi uprawnienia) niż użytkownik, którego używamy podczas działania aplikacji. Po więcej informacji odsyłam do dokumentacji – https://docs.microsoft.com/pl-pl/ef/ef6/modeling/code-first/migrations/migrate-exe.

Własny migrator

Ostatnim sposobem, który ostatnio wykorzystuje coraz częściej, jest stworzenie własnego migratora. Jest to prosta aplikacja konsolowa, do której mogę przekazywać różne parametry, aby odpowiednio zaktualizować aplikację.

Późniejsze użycie tej aplikacji jest bardzo podobne do użycia migrate.exe, ale daje też dużo większe możliwości. Mogę dodać parametr, który będzie decydował, czy do bazy mają zostać dodane dane testowe, czy tylko ma odbyć się aktualizacja schematu bazy. Mogę również dodać parametr, który odtworzy z backup jedną z baz testowych, którą następnie podniesie do aktualnej wersji. Możliwości jest dużo.

Samo stworzenie takiej aplikacji jest bardzo proste. Wystarczy utworzyć nowy projekt aplikacji konsolowej. Dodać do niej obsługę parametrów za pomocą CommandLineParser. W parametrze możemy przekazać connection stringa do bazy, którą chcemy zaktualizować:

Następnie w klasie Program odczytujemy parametry i wykonujemy aktualizację bazy:

Dodatkowy logger na końcu metody Migrate służy do zapisu logów do nLoga, który następnie zapisuje logi do pliku tekstowego oraz wyświetla na konsoli. Sam logger wygląda tak:

Tak przygotowany migrator wystarczy, że uruchomimy z wiersza poleceń, gdzie przekażemy connection stringa do bazy, która ma zostać zaktualizowana:

Wynik działa migratora wygląda na przykład tak:

entity framework own migrator

Przykład

Na github (https://github.com/danielplawgo/EntityFrameworkMigrationExample) znajduje się przykład do wpisu. W przykładzie connection string do bazy znajduje się w dwóch miejscach: web.config aplikacji ASP.NET MVC oraz app.config migratora. Dodatkowo connection string jest również ustawiony jako parametr (właściwości projektu EFMigrationExample.Migrator oraz zakładka Debug). Więc jak chcesz uruchomić migratora, musisz ustawić również tam poprawnego connection string.

Podsumowanie

Mechanizm migracji schematu bazy danych jest ważnym elementem aplikacji. Gdy nie mamy go w projekcie, wtedy na ogół tracimy dużo sił i czasu, aby doprowadzać wszystko do porządku. Szczególnie jest to istotne, gdy wykorzystujemy wiele baz danych. Jednym z sposobów jest wykorzystanie migracji z Entity Framework.

Mechanizm ten jest jednym z przyjemniejszych sposób zarządzania schematem baz danych. Natomiast jego największym problemem jest konieczność wykorzystywana Entity Framework. A nie każdy chce go używać w swoim projekcie. Widziałem też kiedyś projekt, w którym Entity Framework został użyty do zarządzania schematem bazy danych, natomiast Dapper był używany do pracy z danymi w aplikacji.

W następnych dwóch wpisach pokaże inne sposoby na migrację schematu bazy danych. W pierwszej kolejności zobacz Fluent Migratora.

1 thought on “Migracja schematu bazy danych w Entity Framework

Dodaj komentarz

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