[{"data":1,"prerenderedAt":1287},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-ef-migrations":3},{"id":4,"title":5,"body":6,"description":1273,"difficulty":1274,"extension":1275,"framework":1276,"frameworkSlug":183,"meta":1277,"navigation":105,"order":60,"path":1278,"qaPath":1279,"seo":1280,"stem":1281,"subtopic":1282,"topic":1283,"topicSlug":1284,"updated":1285,"__hash__":1286},"blog\u002Fblog\u002Fdotnet-ef-migrations.md","Working with EF Core Migrations",{"type":7,"value":8,"toc":1252},"minimark",[9,14,27,31,41,141,152,156,278,286,290,293,298,301,347,353,357,441,503,506,510,587,596,616,620,631,715,718,760,764,768,782,811,817,821,923,927,993,996,1005,1008,1026,1030,1076,1079,1083,1086,1101,1143,1147,1150,1182,1185,1189,1192,1228,1231,1235,1248],[10,11,13],"h2",{"id":12},"why-migration-knowledge-matters-in-interviews","Why migration knowledge matters in interviews",[15,16,17,18,22,23,26],"p",{},"Schema management is where EF Core projects most often break in production. Interviewers\ntest migrations because the mistakes — running ",[19,20,21],"code",{},"dotnet ef database update"," against production,\nauto-migrating in a multi-replica deploy, or never writing a ",[19,24,25],{},"Down"," method — are expensive\nto fix after the fact.",[10,28,30],{"id":29},"what-migrations-are","What migrations are",[15,32,33,34,37,38,40],{},"EF Core migrations are versioned C# classes that translate model changes into database schema\noperations. Each migration has an ",[19,35,36],{},"Up"," (apply) and a ",[19,39,25],{}," (revert) method.",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F Generated by: dotnet ef migrations add AddOrderStatus\npublic partial class AddOrderStatus : Migration\n{\n    protected override void Up(MigrationBuilder mb)\n    {\n        mb.AddColumn\u003Cstring>(\"Status\", \"Orders\", \"nvarchar(50)\", false, \"Pending\");\n        mb.CreateIndex(\"IX_Orders_Status\", \"Orders\", \"Status\");\n    }\n\n    protected override void Down(MigrationBuilder mb)\n    {\n        mb.DropIndex(\"IX_Orders_Status\", \"Orders\");\n        mb.DropColumn(\"Status\", \"Orders\");\n    }\n}\n","csharp","",[19,49,50,58,64,70,76,82,88,94,100,107,113,118,124,130,135],{"__ignoreMap":47},[51,52,55],"span",{"class":53,"line":54},"line",1,[51,56,57],{},"\u002F\u002F Generated by: dotnet ef migrations add AddOrderStatus\n",[51,59,61],{"class":53,"line":60},2,[51,62,63],{},"public partial class AddOrderStatus : Migration\n",[51,65,67],{"class":53,"line":66},3,[51,68,69],{},"{\n",[51,71,73],{"class":53,"line":72},4,[51,74,75],{},"    protected override void Up(MigrationBuilder mb)\n",[51,77,79],{"class":53,"line":78},5,[51,80,81],{},"    {\n",[51,83,85],{"class":53,"line":84},6,[51,86,87],{},"        mb.AddColumn\u003Cstring>(\"Status\", \"Orders\", \"nvarchar(50)\", false, \"Pending\");\n",[51,89,91],{"class":53,"line":90},7,[51,92,93],{},"        mb.CreateIndex(\"IX_Orders_Status\", \"Orders\", \"Status\");\n",[51,95,97],{"class":53,"line":96},8,[51,98,99],{},"    }\n",[51,101,103],{"class":53,"line":102},9,[51,104,106],{"emptyLinePlaceholder":105},true,"\n",[51,108,110],{"class":53,"line":109},10,[51,111,112],{},"    protected override void Down(MigrationBuilder mb)\n",[51,114,116],{"class":53,"line":115},11,[51,117,81],{},[51,119,121],{"class":53,"line":120},12,[51,122,123],{},"        mb.DropIndex(\"IX_Orders_Status\", \"Orders\");\n",[51,125,127],{"class":53,"line":126},13,[51,128,129],{},"        mb.DropColumn(\"Status\", \"Orders\");\n",[51,131,133],{"class":53,"line":132},14,[51,134,99],{},[51,136,138],{"class":53,"line":137},15,[51,139,140],{},"}\n",[15,142,143,144,147,148,151],{},"EF Core tracks applied migrations in the ",[19,145,146],{},"__EFMigrationsHistory"," table. ",[19,149,150],{},"MigrateAsync"," is\nidempotent — it skips migrations already in the table.",[10,153,155],{"id":154},"the-standard-workflow","The standard workflow",[42,157,161],{"className":158,"code":159,"language":160,"meta":47,"style":47},"language-bash shiki shiki-themes github-light github-dark","# 1. Change the model (add property, entity, or relationship)\n\n# 2. Generate the migration:\ndotnet ef migrations add AddOrderStatus \\\n    --project MyApp.Data \\\n    --startup-project MyApp.Api\n\n# 3. Review the generated Up\u002FDown — always read it before committing\n\n# 4. Apply to local dev DB:\ndotnet ef database update\n\n# 5. Commit migration files alongside the model change in one PR\n\n# 6. CI applies the migration against an integration test database\n\n# 7. Production: MigrateAsync at startup or a migration bundle in the deploy step\n","bash",[19,162,163,169,173,178,201,211,219,223,228,232,237,249,253,258,262,267,272],{"__ignoreMap":47},[51,164,165],{"class":53,"line":54},[51,166,168],{"class":167},"sJ8bj","# 1. Change the model (add property, entity, or relationship)\n",[51,170,171],{"class":53,"line":60},[51,172,106],{"emptyLinePlaceholder":105},[51,174,175],{"class":53,"line":66},[51,176,177],{"class":167},"# 2. Generate the migration:\n",[51,179,180,184,188,191,194,197],{"class":53,"line":72},[51,181,183],{"class":182},"sScJk","dotnet",[51,185,187],{"class":186},"sZZnC"," ef",[51,189,190],{"class":186}," migrations",[51,192,193],{"class":186}," add",[51,195,196],{"class":186}," AddOrderStatus",[51,198,200],{"class":199},"sj4cs"," \\\n",[51,202,203,206,209],{"class":53,"line":78},[51,204,205],{"class":199},"    --project",[51,207,208],{"class":186}," MyApp.Data",[51,210,200],{"class":199},[51,212,213,216],{"class":53,"line":84},[51,214,215],{"class":199},"    --startup-project",[51,217,218],{"class":186}," MyApp.Api\n",[51,220,221],{"class":53,"line":90},[51,222,106],{"emptyLinePlaceholder":105},[51,224,225],{"class":53,"line":96},[51,226,227],{"class":167},"# 3. Review the generated Up\u002FDown — always read it before committing\n",[51,229,230],{"class":53,"line":102},[51,231,106],{"emptyLinePlaceholder":105},[51,233,234],{"class":53,"line":109},[51,235,236],{"class":167},"# 4. Apply to local dev DB:\n",[51,238,239,241,243,246],{"class":53,"line":115},[51,240,183],{"class":182},[51,242,187],{"class":186},[51,244,245],{"class":186}," database",[51,247,248],{"class":186}," update\n",[51,250,251],{"class":53,"line":120},[51,252,106],{"emptyLinePlaceholder":105},[51,254,255],{"class":53,"line":126},[51,256,257],{"class":167},"# 5. Commit migration files alongside the model change in one PR\n",[51,259,260],{"class":53,"line":132},[51,261,106],{"emptyLinePlaceholder":105},[51,263,264],{"class":53,"line":137},[51,265,266],{"class":167},"# 6. CI applies the migration against an integration test database\n",[51,268,270],{"class":53,"line":269},16,[51,271,106],{"emptyLinePlaceholder":105},[51,273,275],{"class":53,"line":274},17,[51,276,277],{"class":167},"# 7. Production: MigrateAsync at startup or a migration bundle in the deploy step\n",[15,279,280,281,285],{},"The key rule: ",[282,283,284],"strong",{},"commit migration files with the model change"," — always in the same PR.",[10,287,289],{"id":288},"applying-migrations-in-production","Applying migrations in production",[15,291,292],{},"Two options, with different trade-offs:",[294,295,297],"h3",{"id":296},"option-a-migrateasync-at-startup","Option A — MigrateAsync at startup",[15,299,300],{},"Simple, works for single-server deployments:",[42,302,304],{"className":44,"code":303,"language":46,"meta":47,"style":47},"var app = builder.Build();\n\nusing (var scope = app.Services.CreateScope())\n{\n    var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n    await db.Database.MigrateAsync(); \u002F\u002F idempotent; skips applied migrations\n}\n\nawait app.RunAsync();\n",[19,305,306,311,315,320,324,329,334,338,342],{"__ignoreMap":47},[51,307,308],{"class":53,"line":54},[51,309,310],{},"var app = builder.Build();\n",[51,312,313],{"class":53,"line":60},[51,314,106],{"emptyLinePlaceholder":105},[51,316,317],{"class":53,"line":66},[51,318,319],{},"using (var scope = app.Services.CreateScope())\n",[51,321,322],{"class":53,"line":72},[51,323,69],{},[51,325,326],{"class":53,"line":78},[51,327,328],{},"    var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n",[51,330,331],{"class":53,"line":84},[51,332,333],{},"    await db.Database.MigrateAsync(); \u002F\u002F idempotent; skips applied migrations\n",[51,335,336],{"class":53,"line":90},[51,337,140],{},[51,339,340],{"class":53,"line":96},[51,341,106],{"emptyLinePlaceholder":105},[51,343,344],{"class":53,"line":102},[51,345,346],{},"await app.RunAsync();\n",[15,348,349,352],{},[282,350,351],{},"Problem:"," In a multi-replica deployment (Kubernetes, App Service), multiple instances\nrace to apply the same migration simultaneously. Use a migration bundle instead.",[294,354,356],{"id":355},"option-b-migration-bundle-preferred-for-containers","Option B — Migration bundle (preferred for containers)",[42,358,360],{"className":158,"code":359,"language":160,"meta":47,"style":47},"# Build once in CI:\ndotnet ef migrations bundle \\\n    --self-contained \\\n    --runtime linux-x64 \\\n    -o .\u002Fmigrate \\\n    --project MyApp.Data \\\n    --startup-project MyApp.Api\n\n# Run in a Kubernetes init container or deploy step:\n.\u002Fmigrate --connection \"Server=prod;Database=App;...\"\n",[19,361,362,367,380,387,397,407,415,421,425,430],{"__ignoreMap":47},[51,363,364],{"class":53,"line":54},[51,365,366],{"class":167},"# Build once in CI:\n",[51,368,369,371,373,375,378],{"class":53,"line":60},[51,370,183],{"class":182},[51,372,187],{"class":186},[51,374,190],{"class":186},[51,376,377],{"class":186}," bundle",[51,379,200],{"class":199},[51,381,382,385],{"class":53,"line":66},[51,383,384],{"class":199},"    --self-contained",[51,386,200],{"class":199},[51,388,389,392,395],{"class":53,"line":72},[51,390,391],{"class":199},"    --runtime",[51,393,394],{"class":186}," linux-x64",[51,396,200],{"class":199},[51,398,399,402,405],{"class":53,"line":78},[51,400,401],{"class":199},"    -o",[51,403,404],{"class":186}," .\u002Fmigrate",[51,406,200],{"class":199},[51,408,409,411,413],{"class":53,"line":84},[51,410,205],{"class":199},[51,412,208],{"class":186},[51,414,200],{"class":199},[51,416,417,419],{"class":53,"line":90},[51,418,215],{"class":199},[51,420,218],{"class":186},[51,422,423],{"class":53,"line":96},[51,424,106],{"emptyLinePlaceholder":105},[51,426,427],{"class":53,"line":102},[51,428,429],{"class":167},"# Run in a Kubernetes init container or deploy step:\n",[51,431,432,435,438],{"class":53,"line":109},[51,433,434],{"class":182},".\u002Fmigrate",[51,436,437],{"class":199}," --connection",[51,439,440],{"class":186}," \"Server=prod;Database=App;...\"\n",[42,442,446],{"className":443,"code":444,"language":445,"meta":47,"style":47},"language-yaml shiki shiki-themes github-light github-dark","# Kubernetes init container:\ninitContainers:\n  - name: db-migrate\n    image: myapp-migrate:latest\n    command: [\".\u002Fmigrate\"]\n    env:\n      - name: ConnectionStrings__Default\n        valueFrom:\n          secretKeyRef:\n            name: db-secret\n            key: connection-string\n","yaml",[19,447,448,453,458,463,468,473,478,483,488,493,498],{"__ignoreMap":47},[51,449,450],{"class":53,"line":54},[51,451,452],{},"# Kubernetes init container:\n",[51,454,455],{"class":53,"line":60},[51,456,457],{},"initContainers:\n",[51,459,460],{"class":53,"line":66},[51,461,462],{},"  - name: db-migrate\n",[51,464,465],{"class":53,"line":72},[51,466,467],{},"    image: myapp-migrate:latest\n",[51,469,470],{"class":53,"line":78},[51,471,472],{},"    command: [\".\u002Fmigrate\"]\n",[51,474,475],{"class":53,"line":84},[51,476,477],{},"    env:\n",[51,479,480],{"class":53,"line":90},[51,481,482],{},"      - name: ConnectionStrings__Default\n",[51,484,485],{"class":53,"line":96},[51,486,487],{},"        valueFrom:\n",[51,489,490],{"class":53,"line":102},[51,491,492],{},"          secretKeyRef:\n",[51,494,495],{"class":53,"line":109},[51,496,497],{},"            name: db-secret\n",[51,499,500],{"class":53,"line":115},[51,501,502],{},"            key: connection-string\n",[15,504,505],{},"The bundle runs before the application starts, guaranteeing a single migration executor.",[10,507,509],{"id":508},"the-__efmigrationshistory-table","The __EFMigrationsHistory table",[42,511,515],{"className":512,"code":513,"language":514,"meta":47,"style":47},"language-sql shiki shiki-themes github-light github-dark","-- Created automatically by EF Core:\nCREATE TABLE __EFMigrationsHistory (\n    MigrationId    nvarchar(150) NOT NULL PRIMARY KEY,\n    ProductVersion nvarchar(32)  NOT NULL\n);\n","sql",[19,516,517,522,538,564,582],{"__ignoreMap":47},[51,518,519],{"class":53,"line":54},[51,520,521],{"class":167},"-- Created automatically by EF Core:\n",[51,523,524,528,531,534],{"class":53,"line":60},[51,525,527],{"class":526},"szBVR","CREATE",[51,529,530],{"class":526}," TABLE",[51,532,533],{"class":182}," __EFMigrationsHistory",[51,535,537],{"class":536},"sVt8B"," (\n",[51,539,540,543,546,549,552,555,558,561],{"class":53,"line":66},[51,541,542],{"class":536},"    MigrationId    ",[51,544,545],{"class":526},"nvarchar",[51,547,548],{"class":536},"(",[51,550,551],{"class":199},"150",[51,553,554],{"class":536},") ",[51,556,557],{"class":526},"NOT NULL",[51,559,560],{"class":526}," PRIMARY KEY",[51,562,563],{"class":536},",\n",[51,565,566,569,571,573,576,579],{"class":53,"line":72},[51,567,568],{"class":536},"    ProductVersion ",[51,570,545],{"class":526},[51,572,548],{"class":536},[51,574,575],{"class":199},"32",[51,577,578],{"class":536},")  ",[51,580,581],{"class":526},"NOT NULL\n",[51,583,584],{"class":53,"line":78},[51,585,586],{"class":536},");\n",[15,588,589,591,592,595],{},[19,590,150],{}," checks this table and skips any migration whose ",[19,593,594],{},"MigrationId"," is already there.\nCheck pending migrations programmatically:",[42,597,599],{"className":44,"code":598,"language":46,"meta":47,"style":47},"var pending = await db.Database.GetPendingMigrationsAsync();\nif (pending.Any())\n    logger.LogWarning(\"{Count} migration(s) pending\", pending.Count());\n",[19,600,601,606,611],{"__ignoreMap":47},[51,602,603],{"class":53,"line":54},[51,604,605],{},"var pending = await db.Database.GetPendingMigrationsAsync();\n",[51,607,608],{"class":53,"line":60},[51,609,610],{},"if (pending.Any())\n",[51,612,613],{"class":53,"line":66},[51,614,615],{},"    logger.LogWarning(\"{Count} migration(s) pending\", pending.Count());\n",[10,617,619],{"id":618},"design-time-factory","Design-time factory",[15,621,622,623,626,627,630],{},"When ",[19,624,625],{},"AppDbContext"," lives in a class library, migration tooling can't discover it automatically.\nCreate an ",[19,628,629],{},"IDesignTimeDbContextFactory\u003CT>",":",[42,632,634],{"className":44,"code":633,"language":46,"meta":47,"style":47},"public class AppDbContextFactory : IDesignTimeDbContextFactory\u003CAppDbContext>\n{\n    public AppDbContext CreateDbContext(string[] args)\n    {\n        var config = new ConfigurationBuilder()\n            .SetBasePath(Directory.GetCurrentDirectory())\n            .AddJsonFile(\"appsettings.json\")\n            .AddEnvironmentVariables()\n            .Build();\n\n        var options = new DbContextOptionsBuilder\u003CAppDbContext>()\n            .UseSqlServer(config.GetConnectionString(\"Default\"))\n            .Options;\n\n        return new AppDbContext(options);\n    }\n}\n",[19,635,636,641,645,650,654,659,664,669,674,679,683,688,693,698,702,707,711],{"__ignoreMap":47},[51,637,638],{"class":53,"line":54},[51,639,640],{},"public class AppDbContextFactory : IDesignTimeDbContextFactory\u003CAppDbContext>\n",[51,642,643],{"class":53,"line":60},[51,644,69],{},[51,646,647],{"class":53,"line":66},[51,648,649],{},"    public AppDbContext CreateDbContext(string[] args)\n",[51,651,652],{"class":53,"line":72},[51,653,81],{},[51,655,656],{"class":53,"line":78},[51,657,658],{},"        var config = new ConfigurationBuilder()\n",[51,660,661],{"class":53,"line":84},[51,662,663],{},"            .SetBasePath(Directory.GetCurrentDirectory())\n",[51,665,666],{"class":53,"line":90},[51,667,668],{},"            .AddJsonFile(\"appsettings.json\")\n",[51,670,671],{"class":53,"line":96},[51,672,673],{},"            .AddEnvironmentVariables()\n",[51,675,676],{"class":53,"line":102},[51,677,678],{},"            .Build();\n",[51,680,681],{"class":53,"line":109},[51,682,106],{"emptyLinePlaceholder":105},[51,684,685],{"class":53,"line":115},[51,686,687],{},"        var options = new DbContextOptionsBuilder\u003CAppDbContext>()\n",[51,689,690],{"class":53,"line":120},[51,691,692],{},"            .UseSqlServer(config.GetConnectionString(\"Default\"))\n",[51,694,695],{"class":53,"line":126},[51,696,697],{},"            .Options;\n",[51,699,700],{"class":53,"line":132},[51,701,106],{"emptyLinePlaceholder":105},[51,703,704],{"class":53,"line":137},[51,705,706],{},"        return new AppDbContext(options);\n",[51,708,709],{"class":53,"line":269},[51,710,99],{},[51,712,713],{"class":53,"line":274},[51,714,140],{},[15,716,717],{},"Then specify both projects in the CLI:",[42,719,721],{"className":158,"code":720,"language":160,"meta":47,"style":47},"dotnet ef migrations add InitialCreate \\\n    --project MyApp.Data \\          # where DbContext lives\n    --startup-project MyApp.Api     # where appsettings.json lives\n",[19,722,723,738,750],{"__ignoreMap":47},[51,724,725,727,729,731,733,736],{"class":53,"line":54},[51,726,183],{"class":182},[51,728,187],{"class":186},[51,730,190],{"class":186},[51,732,193],{"class":186},[51,734,735],{"class":186}," InitialCreate",[51,737,200],{"class":199},[51,739,740,742,744,747],{"class":53,"line":60},[51,741,205],{"class":199},[51,743,208],{"class":186},[51,745,746],{"class":199}," \\ ",[51,748,749],{"class":167},"         # where DbContext lives\n",[51,751,752,754,757],{"class":53,"line":66},[51,753,215],{"class":182},[51,755,756],{"class":186}," MyApp.Api",[51,758,759],{"class":167},"     # where appsettings.json lives\n",[10,761,763],{"id":762},"data-seeding","Data seeding",[294,765,767],{"id":766},"model-based-seeding-static-reference-data","Model-based seeding (static reference data)",[15,769,770,773,774,777,778,781],{},[19,771,772],{},"HasData"," seeds become part of the migration — EF tracks them and generates ",[19,775,776],{},"InsertData"," \u002F\n",[19,779,780],{},"UpdateData"," operations automatically:",[42,783,785],{"className":44,"code":784,"language":46,"meta":47,"style":47},"modelBuilder.Entity\u003CCategory>().HasData(\n    new Category { Id = 1, Name = \"Electronics\", Slug = \"electronics\" },\n    new Category { Id = 2, Name = \"Clothing\",    Slug = \"clothing\"    }\n);\n\u002F\u002F dotnet ef migrations add SeedCategories generates an InsertData migration\n",[19,786,787,792,797,802,806],{"__ignoreMap":47},[51,788,789],{"class":53,"line":54},[51,790,791],{},"modelBuilder.Entity\u003CCategory>().HasData(\n",[51,793,794],{"class":53,"line":60},[51,795,796],{},"    new Category { Id = 1, Name = \"Electronics\", Slug = \"electronics\" },\n",[51,798,799],{"class":53,"line":66},[51,800,801],{},"    new Category { Id = 2, Name = \"Clothing\",    Slug = \"clothing\"    }\n",[51,803,804],{"class":53,"line":72},[51,805,586],{},[51,807,808],{"class":53,"line":78},[51,809,810],{},"\u002F\u002F dotnet ef migrations add SeedCategories generates an InsertData migration\n",[15,812,813,814,816],{},"Constraints: PKs must be set explicitly (no DB-generated values). Changing a row generates\nan ",[19,815,780],{}," migration.",[294,818,820],{"id":819},"custom-seeder-dynamic-or-environment-specific-data","Custom seeder (dynamic or environment-specific data)",[42,822,824],{"className":44,"code":823,"language":46,"meta":47,"style":47},"public static class DatabaseSeeder\n{\n    public static async Task SeedAsync(AppDbContext db)\n    {\n        if (await db.Users.AnyAsync(u => u.Email == \"admin@example.com\"))\n            return; \u002F\u002F idempotent\n\n        db.Users.Add(new User\n        {\n            Email    = \"admin@example.com\",\n            Role     = \"Admin\",\n            Password = PasswordHasher.Hash(\"initial-password\")\n        });\n        await db.SaveChangesAsync();\n    }\n}\n\n\u002F\u002F At startup, after MigrateAsync:\nawait db.Database.MigrateAsync();\nawait DatabaseSeeder.SeedAsync(db);\n",[19,825,826,831,835,840,844,849,854,858,863,868,873,878,883,888,893,897,901,905,911,917],{"__ignoreMap":47},[51,827,828],{"class":53,"line":54},[51,829,830],{},"public static class DatabaseSeeder\n",[51,832,833],{"class":53,"line":60},[51,834,69],{},[51,836,837],{"class":53,"line":66},[51,838,839],{},"    public static async Task SeedAsync(AppDbContext db)\n",[51,841,842],{"class":53,"line":72},[51,843,81],{},[51,845,846],{"class":53,"line":78},[51,847,848],{},"        if (await db.Users.AnyAsync(u => u.Email == \"admin@example.com\"))\n",[51,850,851],{"class":53,"line":84},[51,852,853],{},"            return; \u002F\u002F idempotent\n",[51,855,856],{"class":53,"line":90},[51,857,106],{"emptyLinePlaceholder":105},[51,859,860],{"class":53,"line":96},[51,861,862],{},"        db.Users.Add(new User\n",[51,864,865],{"class":53,"line":102},[51,866,867],{},"        {\n",[51,869,870],{"class":53,"line":109},[51,871,872],{},"            Email    = \"admin@example.com\",\n",[51,874,875],{"class":53,"line":115},[51,876,877],{},"            Role     = \"Admin\",\n",[51,879,880],{"class":53,"line":120},[51,881,882],{},"            Password = PasswordHasher.Hash(\"initial-password\")\n",[51,884,885],{"class":53,"line":126},[51,886,887],{},"        });\n",[51,889,890],{"class":53,"line":132},[51,891,892],{},"        await db.SaveChangesAsync();\n",[51,894,895],{"class":53,"line":137},[51,896,99],{},[51,898,899],{"class":53,"line":269},[51,900,140],{},[51,902,903],{"class":53,"line":274},[51,904,106],{"emptyLinePlaceholder":105},[51,906,908],{"class":53,"line":907},18,[51,909,910],{},"\u002F\u002F At startup, after MigrateAsync:\n",[51,912,914],{"class":53,"line":913},19,[51,915,916],{},"await db.Database.MigrateAsync();\n",[51,918,920],{"class":53,"line":919},20,[51,921,922],{},"await DatabaseSeeder.SeedAsync(db);\n",[10,924,926],{"id":925},"rolling-back","Rolling back",[42,928,930],{"className":158,"code":929,"language":160,"meta":47,"style":47},"# Roll back to a specific migration (that migration stays applied):\ndotnet ef database update AddOrderStatusColumn\n\n# Roll back all migrations:\ndotnet ef database update 0\n\n# Remove the last unapplied migration:\ndotnet ef migrations remove\n",[19,931,932,937,951,955,960,973,977,982],{"__ignoreMap":47},[51,933,934],{"class":53,"line":54},[51,935,936],{"class":167},"# Roll back to a specific migration (that migration stays applied):\n",[51,938,939,941,943,945,948],{"class":53,"line":60},[51,940,183],{"class":182},[51,942,187],{"class":186},[51,944,245],{"class":186},[51,946,947],{"class":186}," update",[51,949,950],{"class":186}," AddOrderStatusColumn\n",[51,952,953],{"class":53,"line":66},[51,954,106],{"emptyLinePlaceholder":105},[51,956,957],{"class":53,"line":72},[51,958,959],{"class":167},"# Roll back all migrations:\n",[51,961,962,964,966,968,970],{"class":53,"line":78},[51,963,183],{"class":182},[51,965,187],{"class":186},[51,967,245],{"class":186},[51,969,947],{"class":186},[51,971,972],{"class":199}," 0\n",[51,974,975],{"class":53,"line":84},[51,976,106],{"emptyLinePlaceholder":105},[51,978,979],{"class":53,"line":90},[51,980,981],{"class":167},"# Remove the last unapplied migration:\n",[51,983,984,986,988,990],{"class":53,"line":96},[51,985,183],{"class":182},[51,987,187],{"class":186},[51,989,190],{"class":186},[51,991,992],{"class":186}," remove\n",[15,994,995],{},"Programmatic rollback:",[42,997,999],{"className":44,"code":998,"language":46,"meta":47,"style":47},"await db.GetService\u003CIMigrator>().MigrateAsync(\"20240101000000_InitialCreate\");\n",[19,1000,1001],{"__ignoreMap":47},[51,1002,1003],{"class":53,"line":54},[51,1004,998],{},[15,1006,1007],{},"Production rollback rules:",[1009,1010,1011,1017,1023],"ul",{},[1012,1013,1014,1016],"li",{},[19,1015,25],{}," methods must not cause data loss unless acceptable.",[1012,1018,1019,1020,1022],{},"For column drops and table drops, leave ",[19,1021,25],{}," empty — use a forward migration to recover.",[1012,1024,1025],{},"Always back up before rolling back in production.",[10,1027,1029],{"id":1028},"previewing-the-generated-sql","Previewing the generated SQL",[42,1031,1033],{"className":158,"code":1032,"language":160,"meta":47,"style":47},"# Script all migrations (review before applying):\ndotnet ef migrations script --idempotent\n\n# Dry run (preview without executing):\ndotnet ef database update --dry-run\n",[19,1034,1035,1040,1054,1058,1063],{"__ignoreMap":47},[51,1036,1037],{"class":53,"line":54},[51,1038,1039],{"class":167},"# Script all migrations (review before applying):\n",[51,1041,1042,1044,1046,1048,1051],{"class":53,"line":60},[51,1043,183],{"class":182},[51,1045,187],{"class":186},[51,1047,190],{"class":186},[51,1049,1050],{"class":186}," script",[51,1052,1053],{"class":199}," --idempotent\n",[51,1055,1056],{"class":53,"line":66},[51,1057,106],{"emptyLinePlaceholder":105},[51,1059,1060],{"class":53,"line":72},[51,1061,1062],{"class":167},"# Dry run (preview without executing):\n",[51,1064,1065,1067,1069,1071,1073],{"class":53,"line":78},[51,1066,183],{"class":182},[51,1068,187],{"class":186},[51,1070,245],{"class":186},[51,1072,947],{"class":186},[51,1074,1075],{"class":199}," --dry-run\n",[15,1077,1078],{},"Always run the idempotent script through code review before applying to staging or production.",[10,1080,1082],{"id":1081},"multiple-dbcontexts","Multiple DbContexts",[15,1084,1085],{},"When the application has more than one context, give each its own migration history:",[42,1087,1089],{"className":44,"code":1088,"language":46,"meta":47,"style":47},"options.UseSqlServer(conn, sql =>\n    sql.MigrationsHistoryTable(\"__AppMigrationsHistory\", \"app\"));\n",[19,1090,1091,1096],{"__ignoreMap":47},[51,1092,1093],{"class":53,"line":54},[51,1094,1095],{},"options.UseSqlServer(conn, sql =>\n",[51,1097,1098],{"class":53,"line":60},[51,1099,1100],{},"    sql.MigrationsHistoryTable(\"__AppMigrationsHistory\", \"app\"));\n",[42,1102,1104],{"className":158,"code":1103,"language":160,"meta":47,"style":47},"dotnet ef migrations add InitialCreate --context AppDbContext\ndotnet ef migrations add IdentityInit  --context IdentityDbContext\n",[19,1105,1106,1124],{"__ignoreMap":47},[51,1107,1108,1110,1112,1114,1116,1118,1121],{"class":53,"line":54},[51,1109,183],{"class":182},[51,1111,187],{"class":186},[51,1113,190],{"class":186},[51,1115,193],{"class":186},[51,1117,735],{"class":186},[51,1119,1120],{"class":199}," --context",[51,1122,1123],{"class":186}," AppDbContext\n",[51,1125,1126,1128,1130,1132,1134,1137,1140],{"class":53,"line":60},[51,1127,183],{"class":182},[51,1129,187],{"class":186},[51,1131,190],{"class":186},[51,1133,193],{"class":186},[51,1135,1136],{"class":186}," IdentityInit",[51,1138,1139],{"class":199},"  --context",[51,1141,1142],{"class":186}," IdentityDbContext\n",[10,1144,1146],{"id":1145},"preventing-model-drift-in-ci","Preventing model drift in CI",[15,1148,1149],{},"EF Core 8+ ships a command that exits with a non-zero code if the model snapshot is out\nof sync with the current model:",[42,1151,1153],{"className":158,"code":1152,"language":160,"meta":47,"style":47},"dotnet ef migrations has-pending-model-changes \\\n    --project MyApp.Data \\\n    --startup-project MyApp.Api\n",[19,1154,1155,1168,1176],{"__ignoreMap":47},[51,1156,1157,1159,1161,1163,1166],{"class":53,"line":54},[51,1158,183],{"class":182},[51,1160,187],{"class":186},[51,1162,190],{"class":186},[51,1164,1165],{"class":186}," has-pending-model-changes",[51,1167,200],{"class":199},[51,1169,1170,1172,1174],{"class":53,"line":60},[51,1171,205],{"class":199},[51,1173,208],{"class":186},[51,1175,200],{"class":199},[51,1177,1178,1180],{"class":53,"line":66},[51,1179,215],{"class":199},[51,1181,218],{"class":186},[15,1183,1184],{},"Add this to your CI pipeline. A model change without a migration is a production outage\non the next deploy — catch it at PR time.",[10,1186,1188],{"id":1187},"squashing-migrations","Squashing migrations",[15,1190,1191],{},"When the history grows unwieldy (50+ migrations), create a baseline:",[42,1193,1195],{"className":158,"code":1194,"language":160,"meta":47,"style":47},"# Delete all migration files\n# Regenerate from the current model snapshot:\ndotnet ef migrations add Baseline --ignore-changes\n# Update __EFMigrationsHistory on existing DBs to point to Baseline\n",[19,1196,1197,1202,1207,1223],{"__ignoreMap":47},[51,1198,1199],{"class":53,"line":54},[51,1200,1201],{"class":167},"# Delete all migration files\n",[51,1203,1204],{"class":53,"line":60},[51,1205,1206],{"class":167},"# Regenerate from the current model snapshot:\n",[51,1208,1209,1211,1213,1215,1217,1220],{"class":53,"line":66},[51,1210,183],{"class":182},[51,1212,187],{"class":186},[51,1214,190],{"class":186},[51,1216,193],{"class":186},[51,1218,1219],{"class":186}," Baseline",[51,1221,1222],{"class":199}," --ignore-changes\n",[51,1224,1225],{"class":53,"line":72},[51,1226,1227],{"class":167},"# Update __EFMigrationsHistory on existing DBs to point to Baseline\n",[15,1229,1230],{},"Coordinate with every environment before squashing — all DBs must be at the latest migration.",[10,1232,1234],{"id":1233},"recap","Recap",[15,1236,1237,1238,1240,1241,1243,1244,1247],{},"EF Core migrations version your database schema alongside your code. Always commit them with\nthe model change. Use ",[19,1239,150],{}," for simple single-server apps; use migration bundles for\ncontainers and multi-replica deployments. Create a design-time factory when your context is in\na class library. Seed static data with ",[19,1242,772],{},"; seed dynamic data with a custom idempotent\nseeder. Add ",[19,1245,1246],{},"dotnet ef migrations has-pending-model-changes"," to CI to catch drift before it\nreaches production.",[1249,1250,1251],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":47,"searchDepth":60,"depth":60,"links":1253},[1254,1255,1256,1257,1261,1262,1263,1267,1268,1269,1270,1271,1272],{"id":12,"depth":60,"text":13},{"id":29,"depth":60,"text":30},{"id":154,"depth":60,"text":155},{"id":288,"depth":60,"text":289,"children":1258},[1259,1260],{"id":296,"depth":66,"text":297},{"id":355,"depth":66,"text":356},{"id":508,"depth":60,"text":509},{"id":618,"depth":60,"text":619},{"id":762,"depth":60,"text":763,"children":1264},[1265,1266],{"id":766,"depth":66,"text":767},{"id":819,"depth":66,"text":820},{"id":925,"depth":60,"text":926},{"id":1028,"depth":60,"text":1029},{"id":1081,"depth":60,"text":1082},{"id":1145,"depth":60,"text":1146},{"id":1187,"depth":60,"text":1188},{"id":1233,"depth":60,"text":1234},"EF Core migrations from first principles — how the Up\u002FDown workflow operates, bundles for deployment, seeding data, and the patterns that prevent schema drift from sneaking into CI.","medium","md",".NET Core",{},"\u002Fblog\u002Fdotnet-ef-migrations","\u002Fdotnet\u002Fentity-framework\u002Fmigrations",{"title":5,"description":1273},"blog\u002Fdotnet-ef-migrations","Migrations","Entity Framework Core","entity-framework","2026-06-23","eYF9pwzLh868go0908sgvKGMeED_7h1feQAGbFkTw40",1782244085988]