Migrace Entity Framework Core na produkční databázi můžeš nasadit třemi rozumnými způsoby: zavolat Database.Migrate() při startu aplikace, spustit migration bundle (efbundle, samostatný spustitelný soubor, který vytvoří dotnet ef migrations bundle) jako krok před startem aplikace, nebo vygenerovat idempotentní SQL skript a spustit ho sám. Pro jednu aplikaci na jednom serveru je aplikace migrací při startu přijatelná a jednoduchá. Bundle spuštěný ze startovacího příkazu je lepší výchozí volba, protože odděluje „migrovat“ od „obsluhovat“ a nepřidává žádnou infrastrukturu. Zkontrolovaný SQL skript je správná odpověď, když databázi nevlastní aplikace, ale někdo jiný. Ať zvolíš cokoli, samotné migrace musí jít bezpečně spustit proti běžící aplikaci, a to je disciplína při psaní migrací, ne nastavení nástroje.
Tento článek projde každou metodu s přesnými příkazy a pak se věnuje tomu, co rozhoduje o tom, jestli nasazení dopadne dobře: kontrole toho, co EF vygeneroval, změnám schématu bez rozbití právě běžící verze a tomu, co „rollback“ doopravdy znamená.
Jak fungují migrace EF Core#
Migrace je třída v C# s metodou Up, která mění schéma, a metodou Down, která změnu vrací zpět. Vzniká porovnáním tvého aktuálního modelu se snapshotem modelu z předchozí migrace.
$ dotnet tool install --global dotnet-ef$ dotnet ef migrations add AddOrderStatus$ dotnet ef migrations list$ dotnet ef database updateNástroje potřebují v startovacím projektu balíček Microsoft.EntityFrameworkCore.Design. dotnet-ef můžeš nainstalovat také jako lokální nástroj v manifestu nástrojů, čímž se jeho verze připne k repozitáři - pro tým je to lepší, protože verze nástroje má odpovídat verzi EF Core, kterou projekt používá.
Příkaz migrations add zapíše do složky Migrations tři věci: třídu migrace, designer soubor s metadaty a aktualizovaný ModelSnapshot. Commitni všechny tři. Podle snapshotu pozná další migrace, co se změnilo, a právě merge konflikty v něm odhalí, že dva vývojáři přidávají migrace na oddělených větvích.
Na straně databáze je tabulka __EFMigrationsHistory s ID každé aplikované migrace a verzí EF Core, která ji aplikovala. Každá metoda nasazení migrací čte tuto tabulku, zjistí, které migrace chybí, a aplikuje je popořadě. Stav nic jiného nesleduje, takže ruční zásahy do schématu - index přidaný naspěch rukou - EF nevidí a často se srazí s pozdější migrací, která se pokusí přidat totéž.
Kontrola toho, co EF vygeneroval#
EF generuje migrace z rozdílu a rozdíl nezná tvůj záměr. Každou migraci si přečti, než ji commitneš.
Přejmenování. Přejmenování vlastnosti může být vygenerováno jako přejmenování, nebo jako zahození starého sloupce a přidání nového, podle toho, co se ještě změnilo. Drop a add ztratí všechny hodnoty ve sloupci. Když EF vygeneruje operaci, která může způsobit ztrátu dat, vypíše „An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.“ Ber tu větu jako stopku a pokud jsi chtěl přejmenovat, nahraď vygenerované operace voláním migrationBuilder.RenameColumn(...).
Nové sloupce, které nesmí být null. Přidání povinné vlastnosti do tabulky s řádky vytvoří sloupec s výchozí hodnotou daného typu - prázdný řetězec, nulu, 0001-01-01. To jen zřídka odpovídá tomu, co mají data říkat. Buď nejdřív udělej sloupec nullable a doplň ho (viz sekce o expand-contract níže), nebo mu dej záměrnou výchozí hodnotu přes HasDefaultValue či defaultValue: v migraci.
Změny dat. Migrace umí spouštět SQL stejně jako měnit schéma:
protected override void Up(MigrationBuilder migrationBuilder){ migrationBuilder.AddColumn<string>( name: "Status", table: "Orders", nullable: true); migrationBuilder.Sql( "UPDATE Orders SET Status = 'paid' WHERE PaidAt IS NOT NULL");}Změny dat drž malé a množinové. Migrace, která načte entity do C# a prochází je ve smyčce, je pomalá, závisí na třídách modelu, které se v pozdějších migracích změní, a po aplikaci někde se rozbíjí způsoby, které se těžko opravují.
Příkazy, které nemohou běžet v transakci. Některé operace - CREATE INDEX CONCURRENTLY v PostgreSQL, některé příkazy ALTER DATABASE v SQL Serveru - odmítnou běžet uvnitř transakce. Pro ně předej migrationBuilder.Sql(...) parametr suppressTransaction: true a smiř se s tím, že migrace už není „všechno, nebo nic“.
Čtyři způsoby, jak aplikovat migrace#
| Metoda | Kde běží | Potřebuje SDK na serveru | Hodí se pro |
|---|---|---|---|
dotnet ef database update | Vývojářský stroj nebo CI | Ano | Vývojové databáze |
Database.Migrate() při startu | Uvnitř aplikace | Ne | Jedna instance, jednoduchá nasazení |
Migration bundle (efbundle) | Před aplikací, na stejném serveru | Ne | Výchozí volba pro většinu nasazení |
| Idempotentní SQL skript | Kdekoli, kde DBA spouští SQL | Ne | Kontrolované a schvalované změny |
Spouštět dotnet ef database update proti produkci z notebooku je metoda, které se vyhni. Potřebuje produkční connection string na vývojářském stroji, použije kód, který je zrovna lokálně vyčtený, a nikdo jiný se nedozví, že se to stalo. Používej ji jen pro vývojové databáze.
Aplikace migrací při startu#
Nejjednodušší produkční metoda je jeden blok před tím, než aplikace začne obsluhovat požadavky:
var app = builder.Build();using (var scope = app.Services.CreateScope()){ var db = scope.ServiceProvider.GetRequiredService<ShopDb>(); db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); await db.Database.MigrateAsync();}app.Run();V čem je to dobré: schéma vždy odpovídá kódu, který se právě spouští, a není žádný samostatný krok, na který by se dalo zapomenout. V čem je to špatné:
- Databázový uživatel aplikace potřebuje právo měnit schéma. Aplikace by normálně měla data jen číst a zapisovat. Migrace při startu znamenají, že účet, který tvoje webová aplikace používá každý den, může také zahazovat tabulky.
- Neúspěšná migrace je neúspěšný start. Aplikace nenastartuje, platforma ji restartuje, migrace selže znovu - smyčka restartů, jejíž skutečná příčina je zahrabaná ve stack trace.
- Víc instancí se může předbíhat. Dvě instance startující naráz se mohou obě pokusit aplikovat stejnou migraci. EF Core 9 přidal kolem migrací zámek na úrovni databáze, který tomu brání, ale ve starších verzích je to reálné riziko.
- Dlouhé migrace narazí na timeouty. Výchozí timeout příkazu je 30 sekund; přidání indexu na velkou tabulku může trvat déle, proto výše
SetCommandTimeout.
EF Core 9 také změnil Migrate() tak, že vyhodí chybu, pokud model obsahuje změny, které žádná migrace nepokrývá (PendingModelChangesWarning). To zachytí chybu „zapomněl jsem přidat migraci“ při startu místo u prvního dotazu, který selže. Je to užitečné, ale lidi, kteří přecházejí z EF Core 8, to překvapí.
Pro jednu aplikaci na jednom serveru s databází, kterou aplikace vlastní, je aplikace migrací při startu rozumná volba. Pro migraci použij samostatný privilegovaný connection string a pro aplikaci ten běžný, aby každodenní účet zůstal omezený.
Migration bundly#
Bundle je jeden spustitelný soubor obsahující tvoje migrace a všechno, co je potřeba k jejich aplikaci. Tam, kde běží, nepotřebuje ani SDK, ani tvoje zdrojové kódy:
$ dotnet ef migrations bundle --self-contained -r linux-x64 -o efbundle --force$ ./efbundle --connection "Host=203.0.113.10;Database=shop;Username=shop_owner;Password=..."Bez --connection použije bundle connection string, se kterým je nakonfigurovaný tvůj DbContext, načtený z konfigurace aplikace. --self-contained -r linux-x64 ho zbaví závislosti na runtime .NET na serveru; --force přepíše předchozí bundle.
Bundle sestav v CI vedle aplikace, nasaď obojí a v startovacím příkazu spusť bundle jako první:
./efbundle --connection "$MIGRATIONS_CONNECTION" && exec dotnet Shop.Web.dllPokud migrace selže, && zabrání startu aplikace nad napůl zmigrovaným schématem a chyba je poslední věcí v logu, ne zahrabaná v šumu při startu. Migrace běží pod vlastním connection stringem s právy na schéma, načteným z proměnné prostředí, zatímco aplikace používá účet, který umí data jen číst a zapisovat. Na RE:NODE se proměnné prostředí nastavují na záložce Startup a startovací příkaz se spouští při každém startu - bundle se tedy spustí při každém restartu, nenajde nic k práci a skončí asi za vteřinu. Nasazení ASP.NET Core aplikace z GitHubu popisuje publikování v CI a nasazení výstupu z větve.
Idempotentní SQL skripty#
Skript promění migrace v obyčejné SQL, které si může kdokoli přečíst dřív, než poběží:
$ dotnet ef migrations script --idempotent -o migrate.sql$ dotnet ef migrations script AddOrders AddOrderStatus -o step.sqlPrvní forma vytvoří všechny migrace od začátku, každou obalenou kontrolou proti __EFMigrationsHistory, takže může běžet proti databázi v libovolné verzi a aplikuje jen to, co chybí. Druhá vytvoří SQL mezi dvěma pojmenovanými migracemi, což je to, co dáš ke kontrole pro jedno vydání.
Skripty jsou správná volba, když produkční databázi vlastní databázový administrátor, když změny musí schválit člověk, nebo když účet aplikace nikdy nesmí mít práva na schéma. Jsou také nejupřímnější způsob, jak vidět, co migrace udělá: z čteného SQL se často ukáže, že „malá“ změna přestavuje celou tabulku. Idempotentní skripty mají u některých providerů omezení - některé příkazy se uvnitř podmíněného bloku chovají jinak - proto skript nejdřív spusť proti kopii produkce a až potom proti produkci.
V SQL Serveru výsledek spustíš přes sqlcmd nebo SSMS, v PostgreSQL přes psql -f, v MySQL klientem mysql. Pokud je tvoje databáze na samostatném tarifu hostingu databází, připojíš se s hostem, portem a přihlašovacími údaji z panelu. Připojení pro jednotlivé enginy najdeš v článku .NET s PostgreSQL, MySQL nebo SQL Serverem.
Změna schématu bez rozbití běžící verze#
Během nasazení běží stará verze aplikace chvíli proti novému schématu - nebo, pokud migrace běží před restartem, po celou dobu restartu. Migrace, kterou starý kód nesnese, v tom okně rozbije web. Vzor, který tomu předchází, je expand a poté contract:
- Expand. Přidej nový sloupec jako nullable, nebo novou tabulku. Starý kód ho ignoruje. Nasaď.
- Migrace dat. Nový kód zapisuje do starého i nového sloupce; migrace nebo úloha na pozadí doplní existující řádky. Nasaď.
- Přepnutí. Nový kód čte jen z nového sloupce. Nasaď.
- Contract. Udělej sloupec non-nullable, pokud má být, a starý sloupec zahoď v pozdější migraci, až ho nic nečte. Nasaď.
Čtyři nasazení kvůli přejmenování působí přehnaně, dokud poprvé přejmenování na jeden krok neshodí web na dobu restartu. U malé aplikace s krátkým restartem a klidnými hodinami je rozumný kompromis stáhnout to na dva kroky (expand spolu s přepnutím, pak contract). Zahodit sloupec, který běžící kód pořád vybírá, rozumné není nikdy. Migrace bez výpadku probírá tento vzor na více příkladech a nasazení bez výpadku na malém serveru pokrývá stranu aplikace.
Rollbacky a neúspěšné migrace#
EF Core umí spouštět metody Down: dotnet ef database update AddOrders vrátí všechny migrace po AddOrders a dotnet ef migrations script AddOrderStatus AddOrders vytvoří SQL pro toto vrácení. To je rollback schématu. Není to rollback dat: Down, který znovu přidá zahozený sloupec, ho přidá prázdný.
Praktická pravidla:
- Před každou migrací, která cokoli zahazuje nebo přepisuje, udělej backup. Skutečným rollbackem dat je jen dump, který jsi aspoň jednou obnovil. Zálohy a obnovy databází popisuje, jak na to pořádně; v tarifech hostingu databází na RE:NODE jsou sloty pro backupy v ceně a backup můžeš před nasazením vytvořit na požádání z panelu.
- Radši jdi dopředu. Pokud byla migrace špatná, nová migrace, která ji opraví, je obvykle bezpečnější než vracení, protože se testuje jako každá jiná změna a nezávisí na metodě
Down, kterou nikdo nikdy nespustil. - Věz, jak vypadá částečné selhání. V SQL Serveru a PostgreSQL se každá migrace aplikuje v transakci, takže po selhání zůstane tato migrace neaplikovaná. V MySQL se příkazy DDL potvrzují implicitně, takže migrace, která selže v půlce, může nechat část změn na místě bez záznamu v historii. Oprava znamená přečíst si schéma, změnu ručně dokončit nebo vrátit a teprve potom to zkusit znovu.
- Nikdy neupravuj migraci, která byla aplikována někde sdíleně. Když ji změníš poté, co proběhla na produkci, další prostředí dostane jiné schéma než produkce. Přidej místo toho novou migraci.
dotnet ef migrations removeje jen pro migrace, které nebyly nikde aplikovány.
FAQ#
Mám místo migrací použít EnsureCreated?
Jen pro jednorázové databáze v testech nebo prototypech. EnsureCreated postaví schéma z aktuálního modelu a nevytvoří tabulku historie, takže se do takové databáze migrace později nedají aplikovat bez jejího znovuvytvoření.
Můžu mnoho starých migrací slít do jedné?
Ano, opatrně: smaž staré soubory migrací, přidej jednu novou migraci, která vytvoří aktuální schéma, a na existujících databázích vlož její ID do __EFMigrationsHistory, aby se neaplikovala dvakrát. Většina projektů to nikdy nepotřebuje; stovky migrací stojí málo.
Proč bundle selže s „unable to create an object of type DbContext“?
Nástroje nedokázaly při návrhu sestavit tvůj kontext, obvykle proto, že závisí na konfiguraci, která existuje, jen když aplikace běží. Přidej IDesignTimeDbContextFactory<T>, která kontext vytvoří s connection stringem z prostředí.
Fungují migrace stejně na PostgreSQL, MySQL a SQL Serveru?
Příkazy jsou stejné; vygenerované SQL a transakční chování ne. Pro každého providera, na kterého cílíš, vygeneruj skript a přečti si ho, zvlášť u MySQL, kde se DDL nedá vrátit zpět.
Kde má žít connection string pro migrace?
V proměnné prostředí na serveru, oddělený od vlastního connection stringu aplikace, aby se účet s právy na schéma používal jen k migrování. Konfigurace a secrety v ASP.NET Core popisuje, jak se oba čtou.




Komentáře
Zcela anonymně: žádný účet, žádný e-mail, žádné cookie. Ukládáme jen jméno, které napíšeš, text a čas - nic dalšího. Počet odkazů je omezený a značkování se nevykresluje.