DataToolsPrePostDeploymentScripts_00

Skrypty Pre-deployment i Post-deployment w projekcie bazodanowym Visual Studio

Kontynuujemy naszą przygodę związaną z projektem bazodanowym w Visual Studio. W pierwszej części powiedzieliśmy sobie jak stworzyć projekt bazodanowy, dodawać do niego zmiany oraz publikować je na serwer docelowy. Dziś rozszerzymy nieco tę koncepcję i powiemy jak wykorzystać skrypty przedwdrożeniowe (Pre-deployment) oraz powdrożeniowe (Post-deployment) – zapraszam do lektury.

Wspomniane we wstępie skrypty to nic innego jak kod wykonywany przed głównym skryptem bazy danych oraz bezpośrednio po nim. W dużym skrócie wygląda to tak, że:

  1. Generowany jest skrypt różnicowy pomiędzy projektem a bazą docelową.
  2. Wykonywane są instrukcje zawarte w skrypcie pre-deployment.
  3. Wykonywany jest skrypt wygenerowany w punkcie 1.
  4. Wykonywane są instrukcje zawarte w skrypcie post-deployment.

Warto zwrócić uwagę na fakt, że punkty nr.1 oraz  3 są wykonywane zawsze w całości, a punkt nr 2 może wyglądać różnie w zależności od tego czy wykonujemy deployment całościowy (drop – create) czy też przyrostowy. Jest więc to bardzo ważne aby zrozumieć, że skrypt różnicowy nie jest “świadomy” tego co się dzieje w pre i post deployment.

Jeśli chodzi o wspomniane skrypty to możemy w ramach pojedynczego projektu posiadać maksymalnie po jednym skrypcie pre oraz post-deployment. Nic jednak nie stoi na przeszkodzie aby z jego poziomu wywoływać wiele instrukcji lub wywoływać inne skrypty, o tym jak to zrobić powiemy sobie w dalszej części artykułu.

W tym miejscu ktoś może zadać zasadne pytanie, a mianowicie po co nam tego typu skrypty. Zastosowań jest wiele m.in:

  • przygotowanie danych i struktur do wykonania głównego skryptu bazy danych,
  • kopiowanie danych do zapasowej tabeli i czyszczenie głównej tabeli aby uniknąć błędu “data loss may occur”,
  • wypełnienie sztucznych słowników i wymiarów (np. wymiaru czasu),
  • logowanie informacji o deploymencie,
  • inne.

Zastosowań jest naprawdę wiele, a powyższe to tylko kilka z nich. Nie tracąc czasu przejdźmy do przykładów użycia omawianych funkcjonalności – na starcie stworzyłem projekt bazodanowy (o tym jak to zrobić pisałem tydzień temu – artykuł znajduje się tutaj). Projekt jest bardzo prosty i zawiera jedynie tabelę oraz widok w ramach schematu dbo:

O tym jak dodać foldery i obiekty nie będę się rozpisywał gdyż zrobiłem to w podanym wyżej artykule – zamieszczam jedynie skrypty na wypadek jakby ktoś chciał powtórzyć ćwiczenie:

Przechodząc dalej nie pozostaje nam nic innego jak przejść do meritum, dla porządku dodałem następujące foldery aby móc w nich umieścić odpowiednie skrypty:

Zacznijmy od tworzenia skryptów dodamy je oczywiście z poziomu menu kontekstowego wybierając Add -> Script:

Po wybraniu powyższej opcji pojawi się okno wyboru odpowiedniego rodzaju skryptu, nas będą interesować pierwsze dwa:

Pozostałe dwa to zwykłe skrypty, które mogą być buildowane lub też nie (z praktycznego punktu widzenia Script (Build) oznacza, że kod w skrypcie jest definicją obiektów, które trafią do głównego skryptu do opublikowania).  Stworzyłem zatem po jednym Pre i Post Deployment skrypcie:

Skrypt PreDeployment jak sama nazwa wskazuje będzie wykonywał się przed właściwym skryptem deploymentu. Po jego otwarciu zobaczymy krótki komentarz informujący nas co tak naprawdę możemy w tym miejscu zrobić. W moim scenariuszu testowym będziemy czyścić dane jeśli tabela nie jest pusta. Do tego celu posłuży poniższy kod TSQL:

Po co sprawdzam czy obiekt istnieje? To bardzo ważne żeby sobie uzmysłowić, że są sytuacje, że tej tabeli nie ma na serwerze docelowym więc nie będziemy mogli jej wyczyścić i dostaniemy błąd.  Może pojawić się pytanie jak to nie ma tabeli docelowej? Odpowiedź jest prosta np. podczas pierwszego deploymentu struktur w ogóle nie ma! Innym przykładem może być sytuacja, że jakiś deweloper chce sobie na maszynie deweloperskiej postawić struktury w całości. Zawsze powinniśmy myśleć w taki sposób aby nasze skrypty były uniwersalne i mogły być puszczane za każdym razem bez względu na stan środowiska docelowego.

Przechodząc do skryptu Post Deployment wygląda on następująco:

Prosty INSERT wstawiający wartości do wyczyszczonej wcześniej tabeli. W taki sposób możemy wstawiać różnego rodzaju kategorie do tabel, których nie możemy załadować z systemu źródłowego. Jest to częsty wymóg szczególnie w przypadku projektów hurtowni danych. Oczywiście prezentowany przykład jest jedynie poglądowy i zamiast czyścić i na nowo wstawiać dane lepszym pomysłem byłoby użycie instrukcji MERGE i nieczyszczenie całej tabeli za każdymr azem jednakże na ten moment zostawimy to właśnie w takiej formie.

Przechodząc dalej nie pozostaje nam nic innego tylko wygenerować plik deploymentu czyli klikamy prawym przyciskiem myszy na projekt i wybieramy Publish, w moim przypadku okno publikacji wygląda następująco (nieco bardziej szczegółowo opisałem konfigurację profilu w poprzednim artykule):

Po kliknięciu Generate Script otrzymamy nasz kod, który zostanie wykonany na docelowej instancji. Jego główna część wygląda następująco:

Pierwszy mój deployment nie tylko stworzy obiekty ale również całą bazę danych. Widzimy również jasno w którym momencie wykonuje się PreDeployment – wtedy tabeli CustomerType jeszcze nie ma dlatego też dodałem sprawdzenie czy coś takiego w ogóle istnieje. Następnie wywoływane są operacje DDL, które stworzą obiekty, a następnie PostDeployment uzupełniający tabelę. Po uruchomieniu skryptu mogę spróbować jeszcze raz wygenerować taki skrypt i wtedy zobaczę, że tabela CustomerType oraz towarzyszący jej widok nie zostaną utworzone ze względu na fakt, iż istnieją one na instancji docelowej, jednakże Pre/Post skrypty wykonywane są zawsze bez względu na strukturę docelowej bazy.

Po wykonaniu powyższego testu postanowiłem dodać nową tabelę o nazwie Customer z referencją do stworzonej wcześniej CustomerType. Jej definicja wygląda następująco:

Po wygenerowaniu skrypt różnicowy wygląda bardzo ciekawie:

Skrypty Pre oraz Post pozostały bez zmian i widzimy, że dodana została  nowa tabela oraz referencja klucza obcego z parametrem NOCHECK. Następnie na samym końcu już po wykonaniu PostDeployment skryptu constraint ten został włączony.

W tym miejscu powinna nam się zapalić czerwona lampka. Chodzi mianowicie o to, co się stanie jeśli ktoś doda dane do tabeli Customer, a my będziemy chcieli wrzucić kolejne zmiany?  Odpowiedzi na to pytanie udzielimy po raz kolejny na przykładzie. Powyższy skrypt możemy uruchomić i wszystko powinno zakończyć się bez problemów ponieważ tabela Customer na ten moment jest pusta. Dodajmy do niej dane używając wbudowanego w Visual Studio okna SQL Server Object Explorer o którym wstępnie chciałbym powiedzieć parę słów:

Okno to posiada dwie sekcje, pierwsza widoczna w górnej części posiada połączenia do instancji SQL Server (w tym do wbudowanej w Visual Studio lokalnej instancji localdb). Druga z kolei nazwana Projects umożliwia nam podgląd obiektów w ramach naszych projektów bazodanowych w sposób zbliżony do tego co widzimy w tradycyjnym eksploratorze obiektów Management Studio.

W ramach eksploratora serwera możemy standardowo pracować z bazą danych mniej więcej tak jak w przypadku wspomnianego Management Studio. Oczywiście brakuje niektórych funkcjonalności jednakże w moim odczuciu do typowej pracy deweloperskiej praktycznie mamy wszystko co potrzebne:

Eksplorator projektów z kolei pozwala podejrzeć obiekty znajdujące się w definicji naszych projektów w uporządkowany sposób grupujący obiekty według typu:

Jak możecie zauważyć powyżej eksplorator projektu umożliwia również dodawanie obiektów tak więc jeśli ktoś woli inicjować te operacje z tego poziomu to nic nie stoi na przeszkodzie. Przejdźmy zatem do eksploratora serwera i tam klikając prawym przyciskiem myszy na wybraną bazę danych wybrałem New Query i wpisałem poniższe polecenie INSERT:

W tym momencie tabela na serwerze docelowym posiada dane. Spróbujmy dokonać jeszcze jednej zmiany w projekcie, a mianowicie zmieńmy definicję kolumny FirstName w tabeli dbo.Customer z NVARCHAR(50) na NVARCHAR(40):

Po  wygenerowaniu skryptu różnicowego warto podejrzeć sobie zmiany, które Visual Studio będzie próbował wykonać – dokonamy tego w oknie Data Tools Operations klikając Preview:

Mamy tutaj  skondensowaną informację  o tym czy jakieś tabelę będę przebudowane, jakie operacje zostaną wykonane i czy któreś z nich mogą skutkować utratą danych:

W powyższym opisie widzimy, że zmiana typu danych, której dokonaliśmy może skutkować właśnie taką utratą danych. Gdy przyjrzymy się samemu skryptowi to możemy dostrzec następujący zapis:

Czyli jeśli tabela zawiera jakiekolwiek dane to zwrócony zostanie błąd i skrypt zostanie przerwany. Rozwiązań tego problemu jest wiele jednym z nich jest użycie np. PreDeployment skryptu, skopiowanie danych do tabeli pomocniczej:

Następnie przywrócenie danych z kopii do głównej tabeli w ramach PostDeploymentu:

Kolejna próba deploymentu również kończy się niepowodzeniem ponieważ mamy konflikt klucza obcego:

Nie możemy wyczyścić tabeli CustomerType – dlatego też dodamy w PreDeployment kawałek kodu, który sprawdzi istnienie tego klucza, a następnie go usunie.

By następnie przywrócić go w PostDeployment:

Po tych zmianach już udało nam się wrzucić zmiany na serwer i utrzymać strukturę oraz dane w konsystentnym stanie. Skrypty możemy oczywiście wzbogacać o transakcje, TRY.. CATCH i tym podobne struktury aby w pełni odpowiadały naszym wymogom. Problemy można rozwiązywać na szereg różnych sposobów więc wszystko zależy od podejścia i konkretnej sytuacji, jedyne na co warto zwracać uwagę to fakt, że Pre oraz Post skrypty wykonują się zawsze i powinny być budowane z uwzględnieniem tej cechy.

W ramach niniejszego artykułu chciałbym pokazać również, że omawiane skrypty mogą odwoływać się do kodu zawartego w innych plikach. Zachęcam do logicznego dzielenia operacji na osobne pliki dzięki czemu rozwiązanie będzie bardziej przejrzyste. Aby tego dokonać stworzyłem foldery, które nazwałem ReleaseA czyli będą tam znajdowały się Pre i Post skrypty związane z konkretnym releasem:

Następnie dodałem tam skrypty (znane już menu kontekstowe Add -> Script i jako typ skryptu wybieramy Script (Not in Build)) i każdą logiczną operację umieściłem w osobnym pliku:

Teraz nie pozostaje nic innego jak dodać odwołania do tych plików w głównych plikach Pre oraz Post.  Używamy tutaj względnego odwołania do pliku czyli :r .\ i następnie podajemy ścieżkę do pliku:

Pamiętamy oczywiście o kolejności poszczególnych wywołań. Po wygenerowaniu skyptu deploymentu widzimy, że wszystko działa tak jak należy:

Polecam również dodawać komentarze w postaci instrukcji Print dzięki czemu łątwo śledzić co się dzieje podczas wykonywania tego skryptu. Cykl życia bazy danych może być bardzo skomplikowany i zależy od wielu czynników jednakże w powyższym podejściu zmiany oznaczone jako ReleaseA jak już będą na środowisku produkcyjnym możemy wyrzucić/zakomentować w głównym pliku Pre/Post i dodać nowe zmiany. Jeśli te skrypty nie są nam potrzebne możemy je albo wyrzucić albo z archiwizować dodając nowy folder z aktualnym Releasem:

Wszystko podpięte pod system kontroli wersji może nam znacznie przyspieszyć i ułatwić pracę deweloperską. Tak jak już wielokrotnie podkreślałem podejść jest bardzo wiele z wykorzystaniem przeróżnych technik, powyżej chciałem przedstawić użycie Pre i Post deployment skryptów w oparciu o jedną z nich. Mam nadzieję, że udało mi się osiągnąć cel jaki postawiłem przed sobą pisząc ten artykuł. Pozdrawiam serdecznie i zapraszam do subskrypcji bloga – już w najbliższym czasie będziemy rozszerzać naszą serie o dodatkowe artykuły – pozdrawiam!

Adrian Chodkowski
Follow me

Leave a Reply

Your email address will not be published. Required fields are marked *