Migracja schematu bazy danych w Entity Framework

Wprowadzenie

W obecnych czasach bardzo rzadko tworzy się aplikacje, które nie korzystają z bazy danych. Gdzieś przecież trzeba zapisać dane. Od jakiegoś czasu coraz bardziej popularne stają są bazy nierelacyjne, ale nadal najczęściej używamy baz relacyjnych, takich jak na przykład SQL Server. Jednym z problemów, z jakimi 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órych 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. Tu znów każda wdrożona baza ma nieco 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 właśnie 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ć problemem. Dzisiaj na pierwszy rzut pójdą migracje w Entity Framework. W kolejnych dwóch wpisach pokażę Ci inne biblioteki, które rozwiązują ten problem w trochę inny sposób. Dzięki temu będziesz mógł/mogła wybrać to, co najlepiej odpowie Twoim potrzebom.

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 temu nie musimy w większości przypadków w ogóle dotykać sqla, ale, jak zobaczysz później, nie zawsze możemy z niego zrezygnować.

Najfajniejsze jest to, że Entity Framework pilnuje uruchamiania migracji na bazie. W specjalnej tabeli zapisuje informacje dotyczące tego, 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 (poniższy zrzut ekranu pokazuje testowy 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 wybranym 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 – nazwy 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 spowoduje, że w konsoli wyświetli się dużo więcej informacji podczas wykonywania aktualizacji bazy danych. Między innymi zostaną wyświetlone wszystkie wykonane sqle. Później jeszcze wrócimy do tego, jak można wykonywać migracje w inny sposób.

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ć migracje w praktyce, warto zmienić nieco 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ła wygenerowana przez Entity Framework. Sam ręcznie dodałem dwa sqle w metodzie Up oraz jeden w metodzie Down. Dodatkowe sqle są odpowiedzialne za przeniesienie danych z kolumny Category do nowej tabeli oraz ustawienie odpowiednich CategoryId. W przypadku metody Down po prostu kopiuję 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 pokazuje również, ż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 sqlu.

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. Jednak już w przypadku środowisk testowych i produkcyjnych jest problematyczny.

Jednym z sposób jest skonfigurowanie Entity Framework w ten sposób, aby automatycznie wykonywał 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 za sobą trudności. 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 tej bazy. To 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 z Entity Framework. Mowa o migrate.exe (https://docs.microsoft.com/pl-pl/ef/ef6/modeling/code-first/migrations/migrate-exe). Narzędzie to można znaleźć w katalogu „packages\EntityFramework.6.2.0\tools”:

entity framework migration exe

Możemy skorzystać z tego narzędzia np. podczas buildu aplikacji. Wykonujemy wtedy migracje z wykorzystaniem zupełnie innego użytkownika (z odpowiednimi uprawnieniami), a nie użytkownika, 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 wykorzystuję 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ł o tym, czy do bazy mają zostać dodane dane testowe, czy ma odbyć się jedynie aktualizacja schematu bazy. Mogę również dodać parametr, który odtworzy z backupu 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 oraz dodać do niego 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 je na konsoli. Sam logger wygląda tak:

Tak przygotowany migrator wystarczy uruchomić z wiersza poleceń, w którym 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 githubie (https://github.com/danielplawgo/EntityFrameworkMigrationExample) znajduje się przykład do wpisu. Connection string do bazy występuje w nim 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 jeśli chcesz uruchomić migratora, musisz ustawić tam również poprawnego connection stringa.

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. Jest to istotne, szczególnie gdy wykorzystujemy wiele baz danych. Jedną z metod jest więc właśnie wykorzystanie migracji z Entity Framework.

Mechanizm ten jest jednym z przyjemniejszych sposób zarządzania schematem baz danych. Jego największym problemem jest natomiast konieczność wykorzystywania 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żę Ci inne sposoby na migrację schematu bazy danych. W pierwszej kolejności zobaczysz 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 *