EF Core migrations
What a migration is diffed against, how the history table decides what runs, and how to ship schema changes without downtime.
After this lesson you can
- Explain the model snapshot and the __EFMigrationsHistory table
- Predict which migrations an update applies or reverts
- Choose how migrations are applied in a real deployment
- Plan an expand and contract change for a rename
A migration is a class with an Up method (apply the change) and a
Down method (undo it), generated from your model:
dotnet ef migrations add AddOrderStatus
dotnet ef database update
migrations add compares the current model with the model snapshot
file in the project (ShopDbModelSnapshot.cs), not with any database.
It writes the difference as a new migration and updates the snapshot.
Its ID is a timestamp plus the name, such as
20250301093000_AddOrderStatus, so IDs sort in creation order.
What runs
The database records every applied migration ID in a table called
__EFMigrationsHistory. database update applies every migration
whose ID is not in that table, in ID order, regardless of where the
newest applied one is. database update <Target> also works downwards:
it runs Down for each applied migration after the target, newest
first; database update 0 reverts everything. The model below applies
that rule to three migration IDs and three history tables.
public static class Solution{ static string Pending(List<string> all, List<string> history) { var applied = new HashSet<string>(history); var pending = all.OrderBy(id => id, StringComparer.Ordinal).Where(id => !applied.Contains(id)); return "apply: " + string.Join(", ", pending.DefaultIfEmpty("nothing")); } public static List<string> Run() { var all = new List<string> { "20250101090000_Init", "20250210140000_AddOrders", "20250301093000_AddOrderStatus", }; return new List<string> { Pending(all, new() { "20250101090000_Init" }), Pending(all, new(all)), // AddOrders came from a branch merged after AddOrderStatus had been applied. Pending(all, new() { "20250101090000_Init", "20250301093000_AddOrderStatus" }), }; }}That third case happens after a merge: a branch created its migration before yours was applied, but merged afterwards. It still gets applied, out of timestamp order, because the history table is a set of IDs, not a high-water mark. Two branches that each add a migration also both change the snapshot, which is usually where the merge conflict appears.
Applying migrations in production
dotnet ef migrations script --idempotentproduces SQL that checks the history table before each migration; a DBA or pipeline can review and run it.dotnet ef migrations bundlebuilds a self-contained executable that applies pending migrations, suitable for a deployment step.db.Database.Migrate()at application startup is convenient, but it ties schema changes to app startup: instances that start together contend to migrate (EF Core 9 takes a database lock so that only one applies them), a failed migration stops the app from starting, and the app's database account needs rights to alter the schema.
Changing a schema while the old code is still running
During a rolling deployment the old and new versions of the app run against the same database. A breaking change, such as renaming a column, is therefore split into steps: add the new column; deploy code that writes both columns and still reads the old one; backfill the new column; deploy code that reads and writes only the new one; and drop the old column in a later release, once no running version uses it. This is the expand and contract pattern.
Common mistakes
- Editing a migration that has already been applied somewhere. The
history table says it ran, so the edit never runs there; add a new
migration instead.
migrations removeis only for the newest migration that has not been applied. - Not reading the generated migration. When a change can lose data, such as dropping a column or narrowing a type, the tool warns that an operation may result in data loss; that line deserves review.
- Assuming migrations run in a strict line after the latest one. A merged older migration is applied too, as in the model's third case.
Exercise · Pro
Check a release plan for a rolling deployment
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade