How to Roll Back an EF Core Migration

How to Roll Back an EF Core Migration

6 min read··

databasedevopsef-core

To roll back an applied EF Core migration, run dotnet ef database update <previous-migration> to revert the schema, then dotnet ef migrations remove to delete the migration file and rewind the model snapshot. The order is not negotiable, because the database revert needs the Down method that lives in the file you are about to delete. A migration that was never applied needs migrations remove alone.

Every EF Core migration has a Down method, so rollbacks should be trivial. And they are, right up until someone deletes the migration file before reverting the database.

Now the history table lists a migration your project no longer has, database update cannot generate the revert SQL, and the model snapshot disagrees with both. The fix at that point is manual surgery.

The rollback commands are simple. What matters is the order, and which environment you are pointing at.

Decision flow for undoing a migration: a never-applied migration is removed with migrations remove, an applied local one is reverted with database update then removed, and a merged or deployed one is reversed with a new forward migration

How Do You Undo the Last Applied Migration?

Say your history looks like this:

dotnet ef migrations list

# 20260620093011_AddOrders (applied)
# 20260701141518_AddOrderNotes (applied)   <- want to undo this one

Step 1: revert the database to the previous migration. database update takes a target migration name and runs every Down between the current state and that target:

dotnet ef database update AddOrders

Step 2: remove the migration file. Only after the database no longer contains the migration:

dotnet ef migrations remove

migrations remove deletes the newest migration file and rewinds the model snapshot, which is the part people forget exists. Doing it by hand (deleting the .cs files) leaves the snapshot describing a model with the migration still in it, and your next migration comes out empty or wrong. The snapshot is why the command exists; use it.

If you run the steps in the wrong order, migrations remove actually protects you: it refuses when the migration is applied, telling you to revert first. The people who get hurt are the ones who delete files manually or sync a branch that no longer contains the migration. Which brings us to git.

The Git Branch Trap

The most common way teams corrupt migration state has no EF command in it at all:

  1. You apply AddOrderNotes to your local database while working on a branch.
  2. The branch dies. You switch back to main.
  3. The migration file is gone, but your local database still has the schema change and the history row.

EF Core now considers your database ahead of your project, and there is no file to generate Down SQL from. Your options, in order of preference:

  • Recreate the database if it is disposable local dev. Fastest, cleanest.
  • Check out the dead branch, revert, then switch. Run dotnet ef database update AddOrders while the migration file still exists in your working tree.
  • Manual repair. Undo the schema change with hand-written SQL, then delete the history row:
DELETE FROM "__EFMigrationsHistory"
WHERE "MigrationId" = '20260701141518_AddOrderNotes';

The lesson: revert your local database before deleting or switching away from a branch with unmerged migrations. Make it muscle memory, the same way you run the practices from EF Core migrations best practices.

Rolling Back Migrations That Were Never Applied

If you added a migration and immediately regretted it (wrong name, model not ready), and it never ran anywhere:

dotnet ef migrations remove

Done. No database step, because there is nothing to revert. This is also the correct response to noticing a bad migration during code review: remove it, fix the model, add a fresh one. Never edit a generated migration's Up in place beyond documented customization, and never renumber or hand-rename them.

Production: Scripts and Bundles, Not CLI Commands

dotnet ef database update against production means SDK, source code, and DDL credentials on whatever machine runs it. Do not. The two production-grade options mirror how you deploy forward migrations.

Reviewed SQL scripts. Generate the revert SQL in CI, review it, run it through the same channel as any release script. Note the argument order, from current back to target:

dotnet ef migrations script AddOrderNotes AddOrders --output revert.sql

Migration bundles. A bundle accepts a target migration and will migrate down to it:

./efbundle AddOrders --connection "$DB_CONNECTION"

I covered why bundles beat startup migration, and how to build them in CI, in EF Core migration bundles.

Now the hard truth about production rollbacks: Down methods are only safe for additive changes. Reverting AddColumn runs DropColumn, and every value in that column is gone. Reverting a table rename or a data migration can be outright impossible to express. EF Core generates Down code mechanically; it has no idea whether the operation destroys data, and it will not warn you.

So before any production rollback:

  • Read the generated SQL. All of it.
  • If any statement drops or rewrites data, prefer rolling forward: write a new migration that reverses the intent while preserving data, and deploy it like any release.
  • If you must roll back destructively, snapshot first (a backup, or SELECT INTO the affected columns).

This is also why the expand-and-contract pattern from zero-downtime migrations is worth the ceremony: when every migration is individually additive, every rollback is individually safe, and the old application version keeps working against the new schema in both directions. A rollback of code without a rollback of schema, the most common incident shape, becomes a non-event.

Rolling Back Everything

Two special targets are worth knowing. Reverting all migrations (torching the schema EF manages, useful for local resets):

dotnet ef database update 0

And listing what is applied versus pending when you are not sure where an environment stands:

dotnet ef migrations list --connection "$DB_CONNECTION"

For local development, database update 0 followed by database update is a poor man's rebuild; dropping and recreating the database is usually faster and also resets anything outside EF's control. If your integration tests fight schema drift, that reset-per-run approach is the reliable one, as I argued in fixing flaky Postgres integration tests.

The Rules That Keep Rollbacks Boring

  • Applied migration, local database: database update <previous>, then migrations remove. In that order, always.
  • Unapplied migration: migrations remove alone.
  • Merged or deployed migration: it is immutable. Reverse it with a new forward migration, never by deleting history.
  • Switching branches: revert the local database before abandoning a branch with unmerged migrations.
  • Production: reviewed script or bundle with a target migration, backups before destructive Downs, and a strong bias toward roll-forward.

The deeper the migration is in shared history, the less "rollback" means running Down and the more it means writing a new Up that undoes the intent. That progression, and the full command reference, is laid out in EF Core migrations: a detailed guide.

Summary

Rolling back a migration is dotnet ef database update <previous-migration> followed by dotnet ef migrations remove, and the order is not negotiable: the database revert needs the Down method that lives in the file you are about to delete. migrations remove exists because the model snapshot must rewind with the file; deleting .cs files by hand corrupts the next migration.

Once a migration reaches a shared environment, stop thinking in terms of Down at all. Deployed history is append-only: reverse mistakes with new forward migrations, keep changes additive so old code and old schema stay mutually compatible, and save destructive rollbacks for databases you can afford to lose.

Frequently Asked Questions

How do I undo the last EF Core migration?

If the migration was applied, first run dotnet ef database update with the name of the previous migration to revert the schema, then run dotnet ef migrations remove to delete the migration file and roll back the snapshot. If it was never applied, migrations remove alone is enough.

Why does the order of rollback steps matter?

Reverting the database needs the migration Down method, which lives in the migration file. If you delete the file first, EF Core can no longer generate the revert SQL, and the migrations history table still lists a migration your project no longer contains.

Can I roll back a migration in production?

Yes, by generating a script with dotnet ef migrations script From To (in reverse order) or by running a migration bundle with a target migration. But Down methods for destructive changes drop data, so review the SQL and prefer roll-forward fixes when data loss is possible.

What happens to my data when a migration is rolled back?

Whatever the Down method says. Reverting an added column drops the column and its data permanently. EF Core does not warn you about data loss on rollback, so review the Down SQL before running it against anything you care about.

Should I ever edit or delete a migration that was already deployed?

No. Once a migration has run against any shared or production database, treat it as immutable. Undo its effects with a new migration that reverses the change, so every environment converges by applying the same forward history.

Loading comments...

Whenever you're ready, there are 4 ways I can help you:

  1. Pragmatic Clean Architecture: Join 5,000+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  2. Modular Monolith Architecture: Join 2,800+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  3. Pragmatic REST APIs: Join 1,900+ students in this course that will teach you how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API.
  4. Patreon Community: Join a community of 5,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.

The .NET Weekly

Become a Better .NET Software Engineer

Join 66,000+ engineers who are improving their skills every Saturday morning.