# How to Roll Back an EF Core Migration

> Rolling back an EF Core migration is two commands, but only in the right order. Delete the migration file first and EF Core loses the ability to generate the Down SQL, leaving your database and model out of sync. Here is the safe sequence for local, shared, and production databases.

Published: 2026-08-23. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/ef-core-migration-rollback

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](https://milanjovanovic.tech/blogs/articles/ef-core-migration-rollback/rollback-decision.png)

## How Do You Undo the Last Applied Migration?

Say your history looks like this:

```bash
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:

```bash
dotnet ef database update AddOrders
```

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

```bash
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:

```sql
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**](https://milanjovanovic.tech/blog/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:

```bash
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:

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

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

```bash
./efbundle AddOrders --connection "$DB_CONNECTION"
```

I covered why bundles beat startup migration, and how to build them in CI, in [**EF Core migration bundles**](https://milanjovanovic.tech/blog/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**](https://milanjovanovic.tech/blog/zero-downtime-migrations-ef-core) 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):

```bash
dotnet ef database update 0
```

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

```bash
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 `Down`s, 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**](https://milanjovanovic.tech/blog/efcore-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.
