Migrace Code First v týmových prostředích

Poznámka

Tento článek předpokládá, že víte, jak používat Migrace Code First v základních scénářích. Pokud ne, budete si muset před pokračováním přečíst Migrace Code First.

Vezměte si kávu, musíte si přečíst celý článek.

Problémy v týmových prostředích se většinou týkají sloučení migrací, když dva vývojáři vygenerovali migrace v místním základu kódu. I když jsou tyto kroky poměrně jednoduché, vyžadují, abyste měli solidní přehled o tom, jak migrace fungují. Nepřeskočte na konec – přečtěte si celý článek, abyste se ujistili, že jste úspěšní.

Některé obecné pokyny

Než se podíváme na to, jak spravovat slučování migrací vygenerovaných více vývojáři, tady jsou některé obecné pokyny, které vám pomůžou nastavit úspěch.

Každý člen týmu by měl mít místní vývojovou databázi.

Migrace pomocí tabulky __MigrationsHistory ukládají, jaké migrace byly použity v databázi. Pokud máte více vývojářů, kteří generují různé migrace při pokusu o cílení na stejnou databázi (a proto sdílíte __MigrationsHistory tabulku), budou migrace velmi zmatené.

Pokud máte samozřejmě členy týmu, kteří negenerují migrace, není problém s jejich sdílením centrální vývojové databáze.

Vyhněte se automatickým migracím

Dole je to, že automatické migrace zpočátku vypadají dobře v týmových prostředích, ale ve skutečnosti nefungují. Pokud chcete vědět proč, pokračujte ve čtení – pokud ne, můžete přeskočit na další část.

Automatické migrace umožňují aktualizovat schéma databáze tak, aby odpovídalo aktuálnímu modelu, aniž byste museli generovat soubory kódu (migrace založené na kódu). Automatické migrace by v týmovém prostředí fungovaly velmi dobře, pokud byste je někdy používali a nikdy negenerovali žádné migrace založené na kódu. Problémem je, že automatické migrace jsou omezené a nezpracují řadu operací – přejmenování vlastností nebo sloupců, přesun dat do jiné tabulky atd. Pro zpracování těchto scénářů skončíte generováním migrací založených na kódu (a úpravou vygenerovaného kódu), které jsou smíšené mezi změnami, které se zpracovávají automatickými migracemi. To znemožňuje sloučení změn, když se dva vývojáři přihlásí k migraci.

Principy fungování migrací

Klíčem k úspěšnému používání migrací v týmovém prostředí je základní znalost toho, jak migrace sleduje a používá informace o modelu k detekci změn modelu.

První migrace

Když do projektu přidáte první migraci, spustíte v konzole Správce balíčků něco jako Add-Migration First. Základní kroky, které tento příkaz provede, jsou znázorněny níže.

First Migration

Aktuální model se vypočítá z vašeho kódu (1). Požadované databázové objekty se pak vypočítají podle modelu (2) – protože se jedná o první migraci, která se model liší, pouze pro porovnání používá prázdný model. Požadované změny se předají generátoru kódu pro sestavení požadovaného kódu migrace (3), který se pak přidá do vašeho řešení sady Visual Studio (4).

Kromě skutečného kódu migrace, který je uložený v hlavním souboru kódu, migrace také vygenerují některé další soubory za kódem. Tyto soubory jsou metadata, která používají migrace a které nejsou něco, co byste měli upravit. Jedním z těchto souborů je soubor prostředků (.resx), který obsahuje snímek modelu v době vygenerování migrace. Uvidíte, jak se používá v dalším kroku.

V tomto okamžiku byste pravděpodobně spustili update-Database , abyste použili změny v databázi, a pak byste přešli na implementaci dalších oblastí vaší aplikace.

Následné migrace

Později se vrátíte a provedete nějaké změny modelu – v našem příkladu přidáme vlastnost Url do blogu. Pak byste vydali příkaz, například Add-Migration AddUrl , aby se migrace vygenerovala, aby se použily odpovídající změny databáze. Základní kroky, které tento příkaz provede, jsou znázorněny níže.

Second Migration

Stejně jako při posledním výpočtu aktuálního modelu z kódu (1). Tentokrát však existují migrace, takže předchozí model se načte z nejnovější migrace (2). Tyto dva modely se odlijí, aby se zjistily požadované změny databáze (3) a proces se dokončí jako předtím.

Stejný proces se používá pro všechny další migrace, které do projektu přidáte.

Proč obtěžovat snímek modelu?

Možná vás zajímá, proč EF obtěžuje snímek modelu – proč se jen nedívá na databázi. Pokud ano, přečtěte si dál. Pokud vás to nezajímá, můžete tuto část přeskočit.

Existuje řada důvodů, proč EF uchovává snímek modelu kolem:

  • Umožňuje, aby se vaše databáze odchyluje od modelu EF. Tyto změny je možné provést přímo v databázi nebo můžete změnit vygenerovaný kód v migracích, aby se změny provedly. Tady je několik příkladů v praxi:
    • Do jedné nebo více tabulek chcete přidat vložený a aktualizovaný sloupec, ale nechcete tyto sloupce zahrnout do modelu EF. Pokud se migrace podívala na databázi, pokusí se tyto sloupce průběžně vypustit při každém generování migrace. Pomocí snímku modelu EF rozpozná pouze legitimní změny modelu.
    • Chcete změnit tělo uložené procedury použité pro aktualizace tak, aby zahrnovalo určité protokolování. Pokud se migrace podívala na tuto uloženou proceduru z databáze, zkusí ji průběžně zkusit a obnovit zpět do definice, kterou EF očekává. Pomocí snímku modelu EF změní uloženou proceduru pouze vygenerovaný kód, když změníte tvar procedury v modelu EF.
    • Stejné principy platí i pro přidání dalších indexů, včetně dalších tabulek v databázi, mapování EF na zobrazení databáze, které se nachází nad tabulkou atd.
  • Model EF obsahuje více než jen tvar databáze. Mít celý model umožňuje migracím podívat se na informace o vlastnostech a třídách v modelu a o tom, jak se mapují na sloupce a tabulky. Tyto informace umožňují, aby migrace byly inteligentnější v kódu, který vygeneruje. Pokud například změníte název sloupce, který se mapuje na migrace, může přejmenování zjistit tak, že zjistíte, že se jedná o stejnou vlastnost – něco, co nejde udělat, pokud máte pouze schéma databáze. 

Co způsobuje problémy v týmových prostředích

Pracovní postup popsaný v předchozí části funguje skvěle, když pracujete na aplikaci s jedním vývojářem. Funguje také dobře v týmovém prostředí, pokud jste jedinou osobou, která provádí změny modelu. V tomto scénáři můžete provádět změny modelu, generovat migrace a odesílat je do správy zdrojového kódu. Ostatní vývojáři můžou synchronizovat vaše změny a spustit Update-Database , aby se použily změny schématu.

Problémy začínají nastat, když máte více vývojářů, kteří provádí změny modelu EF a současně je odesílali do správy zdrojového kódu. Nedostatek EF představuje prvotřídní způsob, jak sloučit místní migrace s migracemi, které jiný vývojář odeslal do správy zdrojového kódu od poslední synchronizace.

Příklad konfliktu při sloučení

Nejprve se podíváme na konkrétní příklad takového konfliktu při slučování. Budeme pokračovat v příkladu, na který jsme se podívali dříve. Jako výchozí bod předpokládejme, že změny z předchozí části byly vráceny původním vývojářem se změnami. Při provádění změn základu kódu budeme sledovat dva vývojáře.

Model EF a migrace budeme sledovat prostřednictvím řady změn. Pro výchozí bod se oba vývojáři synchronizovali s úložištěm správy zdrojového kódu, jak je znázorněno na následujícím obrázku.

Starting Point

Vývojář č. 1 a vývojář #2 teď v místním základu kódu změní model EF. Vývojář č. 1 přidá do blogu vlastnost Hodnocení – a vygeneruje migraci AddRating, která použije změny v databázi. Developer č. 2 přidá do blogu vlastnost Čtenáři – a vygeneruje odpovídající migraci AddReaders. Oba vývojáři spustí Update-Database, aby mohli použít změny v místních databázích, a pak pokračovat v vývoji aplikace.

Poznámka

Migrace mají předponu časového razítka, takže náš obrázek představuje migraci AddReaders z Developer #2 po migraci AddRating z Developer #1. Ať už vývojář č. 1 nebo #2 vygeneroval migraci jako první, nezáleží na problémech při práci v týmu nebo na proces jejich sloučení, na který se podíváme v další části.

Local Changes

Vývojáři č. 1 mají štěstí, protože nejdřív odesílají změny. Vzhledem k tomu, že od synchronizace úložiště nikdo jiný nezkontroloval, může pouze odeslat změny bez sloučení.

Submit Changes

Teď je čas, aby vývojář #2 odeslal. Nejsou tak šťastní. Protože někdo jiný od synchronizace odeslal změny, bude muset změny stáhnout a sloučit. Systém správy zdrojového kódu bude pravděpodobně moct automaticky sloučit změny na úrovni kódu, protože jsou velmi jednoduché. Stav místního úložiště Developer #2 po synchronizaci je znázorněn na následujícím obrázku. 

Pull From Source Control

V této fázi vývojář #2 může spustit update-database , která zjistí novou migraci AddRating (která nebyla použita pro databázi vývojáře č. 2) a použije ji. Teď se sloupec Hodnocení přidá do tabulky Blogy a databáze se synchronizuje s modelem.

Existuje ale několik problémů:

  1. I když update-Database použije migraci AddRating , zobrazí se také upozornění: Databázi nelze aktualizovat tak, aby odpovídala aktuálnímu modelu, protože existují čekající změny a automatická migrace je zakázaná... Problém je, že snímek modelu uložený v poslední migraci (AddReader) chybí rating vlastnost na blogu (protože nebyl součástí modelu při vygenerování migrace). Code First zjistí, že model v poslední migraci neodpovídá aktuálnímu modelu a vyvolá upozornění.
  2. Spuštění aplikace způsobí výjimku InvalidOperationException, která hlásí, že se od vytvoření databáze změnil model, který zálohuje kontext BloggingContext. Zvažte použití Migrace Code First k aktualizaci databáze..." Problém je, že snímek modelu uložený v poslední migraci neodpovídá aktuálnímu modelu.
  3. Nakonec bychom očekávali, že spuštění doplňkové migrace teď vygeneruje prázdnou migraci (protože pro databázi se nedají použít žádné změny). Vzhledem k tomu, že migrace porovnávají aktuální model s modelem z poslední migrace (která chybí vlastnost Hodnocení ), ve skutečnosti vygeneruje další volání AddColumn , které se přidá do sloupce Hodnocení . Tato migrace by samozřejmě selhala během aktualizace databáze , protože sloupec Hodnocení již existuje.

Řešení konfliktu při slučování

Dobrou zprávou je, že není příliš těžké se sloučit ručně – za předpokladu, že víte, jak migrace fungují. Takže pokud jste přeskočili dopředu do této části... omlouváme se, musíte se vrátit a přečíst si zbytek článku jako první!

Existují dvě možnosti, nejjednodušší je vygenerovat prázdnou migraci, která má správný aktuální model jako snímek. Druhou možností je aktualizovat snímek v poslední migraci, aby měl správný snímek modelu. Druhá možnost je o něco těžší a nedá se použít v každém scénáři, ale je také čistější, protože nezahrnuje přidání další migrace.

Možnost 1: Přidání prázdné migrace sloučení

V této možnosti vygenerujeme prázdnou migraci výhradně pro účely zajištění, že nejnovější migrace obsahuje správný snímek modelu uložený v ní.

Tuto možnost můžete použít bez ohledu na to, kdo vygeneroval poslední migraci. V příkladu, který sledujeme, se vývojář #2 stará o sloučení a došlo k vygenerování poslední migrace. Stejný postup ale můžete použít, pokud vývojář #1 vygeneroval poslední migraci. Tento postup platí také v případě, že se týká více migrací – právě jsme se podívali na dvě, abychom to mohli jednoduše zachovat.

Pro tento přístup je možné použít následující proces, počínaje časem, kdy zjistíte, že máte změny, které je potřeba synchronizovat ze správy zdrojového kódu.

  1. Ujistěte se, že do migrace byly zapsány všechny čekající změny modelu v místním základu kódu. Tento krok zajistí, že při vygenerování prázdné migrace nezmeškáte žádné legitimní změny.
  2. Synchronizace se správou zdrojového kódu
  3. Spusťte update-Database , aby se použily všechny nové migrace, které se přihlásili jiní vývojáři. Poznámka:Pokud se z příkazu Update-Database nezobrazí žádná upozornění, nebyly žádné nové migrace od jiných vývojářů a není nutné provádět žádné další sloučení.
  4. Spusťte pick_a_name> doplňku – <IgnoreChanges (například Sloučení doplňků – IgnoreChanges). Tím se vygeneruje migrace se všemi metadaty (včetně snímku aktuálního modelu), ale při porovnávání aktuálního modelu se snímkem při poslední migraci ignoruje všechny změny, které detekuje (což znamená, že získáte prázdnou metodu Nahoru a dolů ).
  5. Spusťte Update-Database a znovu nainstalujte nejnovější migraci s aktualizovanými metadaty.
  6. Pokračujte v vývoji nebo odešlete do správy zdrojového kódu (po spuštění testů jednotek samozřejmě).

Tady je stav základu místního kódu vývojáře č. 2 po použití tohoto přístupu.

Merge Migration

Možnost 2: Aktualizace snímku modelu v poslední migraci

Tato možnost je velmi podobná možnosti 1, ale odebere nadbytečnou prázdnou migraci – protože se podíváme na to, kdo chce ve svém řešení další soubory kódu.

Tento přístup je proveditelný pouze v případě, že poslední migrace existuje pouze v místním základu kódu a ještě nebyla odeslána do správy zdrojového kódu (například pokud byla poslední migrace vygenerována uživatelem, který provádí sloučení). Úprava metadat migrací, které už jiní vývojáři použili u své vývojové databáze ( nebo ještě horšího použití v produkční databázi), můžou mít za následek neočekávané vedlejší účinky. Během procesu vrátíme poslední migraci v naší místní databázi a znovu ji použijeme s aktualizovanými metadaty.

I když poslední migrace musí být jenom v místním základu kódu, neexistují žádná omezení počtu nebo pořadí migrací, které ho budou pokračovat. Existuje několik migrací od několika různých vývojářů a stejný postup platí – právě jsme se podívali na dva, abychom to mohli jednoduše zachovat.

Pro tento přístup je možné použít následující proces, počínaje časem, kdy zjistíte, že máte změny, které je potřeba synchronizovat ze správy zdrojového kódu.

  1. Ujistěte se, že do migrace byly zapsány všechny čekající změny modelu v místním základu kódu. Tento krok zajistí, že při vygenerování prázdné migrace nezmeškáte žádné legitimní změny.
  2. Synchronizujte se se správou zdrojového kódu.
  3. Spusťte update-Database , aby se použily všechny nové migrace, které se přihlásili jiní vývojáři. Poznámka:Pokud se z příkazu Update-Database nezobrazí žádná upozornění, nebyly žádné nové migrace od jiných vývojářů a není nutné provádět žádné další sloučení.
  4. Spusťte second_last_migration > Update-Database –TargetMigration <(v příkladu jsme postupovali jako Update-Database –TargetMigration AddRating). Tím se databáze vrátí zpět do stavu druhé poslední migrace – efektivně se zruší použití poslední migrace z databáze. Poznámka:Tento krok je nutný k tomu, aby bylo bezpečné upravovat metadata migrace, protože metadata jsou také uložena v __MigrationsHistoryTable databáze. Proto byste tuto možnost měli použít jenom v případě, že poslední migrace je pouze v místním základu kódu. Pokud by se použila poslední migrace jiných databází, museli byste je vrátit zpět a znovu použít poslední migraci, aby se aktualizovala metadata. 
  5. Spusťte full_name_including_timestamp_of_last_migration> doplňku (<v příkladu, který sledujeme, by to bylo něco jako 201311062215252_AddReaders doplňku). Poznámka:Musíte zahrnout časové razítko, aby migrace věděla, že chcete stávající migraci upravit, a ne vygenerování nového. Tím se aktualizují metadata poslední migrace tak, aby odpovídala aktuálnímu modelu. Po dokončení příkazu se zobrazí následující upozornění, ale přesně to chcete. Znovu se vygeneroval pouze kód návrháře pro migraci 201311062215252_AddReaders. Pokud chcete znovu vygenerovat celou migraci, použijte parametr -Force.
  6. Spusťte Update-Database a znovu nainstalujte nejnovější migraci s aktualizovanými metadaty.
  7. Pokračujte v vývoji nebo odešlete do správy zdrojového kódu (po spuštění testů jednotek samozřejmě).

Tady je stav základu místního kódu vývojáře č. 2 po použití tohoto přístupu.

Updated Metadata

Souhrn

Při používání Migrace Code First v týmovém prostředí dochází k určitým problémům. Základní znalosti o tom, jak migrace fungují, a několik jednoduchých přístupů k řešení konfliktů při slučování usnadňuje řešení těchto problémů.

Základním problémem jsou nesprávná metadata uložená v nejnovější migraci. To způsobí, že Code First nesprávně zjistí, že aktuální schéma modelu a databáze neodpovídá a v další migraci vygeneruje nesprávný kód. Tuto situaci lze překonat generováním prázdné migrace se správným modelem nebo aktualizací metadat v nejnovější migraci.