Database migrations are a routine part of application development. They allow teams to evolve database schemas as application requirements change. A migration might add a column, create an index, modify a relationship, or introduce a new table.
In a development environment, recovering from a failed migration is usually straightforward. In production, the same failure can be more complicated. Some migration operations may have completed before the failure occurred, the database schema may be partially changed, and the application may already be running code that expects the new schema.
The safest recovery approach starts with understanding what actually happened. Rather than immediately rolling back or deleting migration records, inspect the database state, identify which operations succeeded, and choose a recovery strategy that preserves data and application consistency.
1. Understand What Happens When an EF Core Migration Fails
When EF Core applies a migration, it executes the database operations defined in that migration. These operations can include creating tables, adding columns, modifying constraints, and creating indexes.
EF Core also maintains a migration history table, named __EFMigrationsHistory by default. It records which migrations have been successfully applied to the database.
For example, consider this migration:
public partial class AddEmployeeDetails : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Department",
table: "Employees",
type: "nvarchar(100)",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Employees_Department",
table: "Employees",
column: "Department");
}
}
This migration performs two operations:
Adds a Department column to the Employees table.
Creates an index on the Department column.
Suppose the column is added successfully, but the index creation fails because of a database issue.
What happens next depends on the database provider, the operations involved, and transaction behavior.
Many migration operations are executed inside a transaction when supported. If the transaction is rolled back successfully, the earlier operations in that transaction are also reverted. However, some operations cannot run inside a transaction, and custom migration code can explicitly suppress transaction handling.
This means a failed migration does not always leave the database in the same state.
The migration history table is an important source of information, but it does not prove that every individual operation in a failed migration was undone.
2. Stop and Inspect Before Attempting Recovery
The first step after a production migration failure should be to stop additional migration attempts.
Do not immediately rerun the migration, delete its history record, or execute its Down method.
Start by collecting the following information:
The migration name that failed.
The complete database error message.
The SQL statements executed before the failure.
Whether the database transaction was rolled back.
Which schema changes are present in the database.
Whether the failed migration appears in the migration history table.
Whether the application is currently compatible with the database schema.
You can inspect the migration history using SQL:
SELECT *
FROM __EFMigrationsHistory
ORDER BY MigrationId;
This query shows the migrations recorded as successfully applied.
Next, inspect the affected table:
SELECT COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Employees';
You can also inspect the indexes:
SELECT
name,
type_desc
FROM sys.indexes
WHERE object_id = OBJECT_ID('dbo.Employees');
The index query is specific to SQL Server. Other database providers have their own catalog views and inspection commands.
Compare the actual database schema with the migration's intended changes. This helps determine whether the migration was completely rolled back, partially applied, or recorded successfully despite an issue reported by the deployment process.
3. Identify the Migration Failure Scenario
Before choosing a recovery strategy, understand which situation applies.
Scenario |
Database state |
Recommended action |
Migration failed and transaction rolled back |
Earlier changes in the transaction are reverted |
Resolve the original error and retry after validation |
Migration failed after a non-transactional operation |
Some schema changes may remain |
Inspect and reconcile the database before retrying |
Migration is recorded in history but deployment reports failure |
Migration may already be applied |
Verify schema and migration history before taking action |
Application fails after a successful migration |
Schema may be correct, but application compatibility is broken |
Investigate application deployment and compatibility |
Data transformation failed |
Schema changes may exist, but data may be incomplete |
Validate affected data and repair through a controlled script or migration |
This distinction matters because each scenario requires a different recovery approach.
For example, retrying a migration that already created a column can fail with a duplicate-column error. On the other hand, manually deleting the migration history record can cause EF Core to attempt operations that have already been performed.
4. Recover When the Migration Was Fully Rolled Back
This is generally the simplest scenario.
Suppose a migration fails because the database cannot create an index due to an existing duplicate or conflicting index name.
If the migration ran inside a transaction and the rollback completed successfully, the database should return to its previous schema state for the operations in that transaction.
You can then:
Identify the exact database error.
Correct the migration or the underlying database issue.
Generate a new migration if the original migration needs to be replaced.
Generate and inspect the updated SQL.
Apply the corrected migration through the deployment process.
For example, if the original migration attempts to create an index that already exists under another deployment process, inspect whether that index is equivalent to the intended index before changing the migration.
Do not assume that every failed migration requires a new migration. Sometimes the issue is environmental, such as missing permissions, insufficient storage, or a database lock.
The recovery decision should be based on the actual database state.
5. Recover From a Partially Applied Migration
Partial application is more difficult because the database may contain some changes from the failed migration.
Consider a migration that adds two columns:
public partial class AddEmployeeFields : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Department",
table: "Employees",
type: "nvarchar(100)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "LastReviewedAt",
table: "Employees",
type: "datetime2",
nullable: true);
}
}
Imagine the first column was added, but the second operation failed because of a database problem.
Before retrying, verify whether the first column exists and whether the migration is recorded in the history table.
A safe recovery process looks like this:
Take a database backup or confirm that a suitable recovery point exists.
Inspect the actual schema and data.
Compare the database with the intended migration.
Determine which operations were committed.
Decide whether to restore the original state or complete the intended schema change.
Validate the result before allowing the application to continue.
In many cases, completing the intended schema change through a carefully reviewed corrective migration or SQL script is safer than attempting to reverse every operation manually.
If you need to repair the database manually, ensure the final schema matches the migration model before allowing EF Core to proceed with later migrations.
Do not insert a migration record into __EFMigrationsHistory simply to make EF Core believe that a migration succeeded. The record should only represent a migration whose intended database changes have actually been completed and verified.
6. Should You Roll Back a Failed Migration?
Rolling back is one possible recovery strategy, but it should not be the automatic response to every failure.
EF Core migrations support an Up method for applying changes and a Down method for reversing them.
For example:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Department",
table: "Employees",
type: "nvarchar(100)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Department",
table: "Employees");
}
You can use EF Core tooling to move the database to an earlier migration:
dotnet ef database update PreviousMigration
However, this command is intended to move a database between migration states. It is not a general-purpose repair command for a partially applied migration.
Before rolling back, consider:
Does the Down method remove production data?
Have users started writing data into the new columns?
Does the rollback affect other application versions?
Are there dependent tables, indexes, or constraints?
Can the rollback be tested against a recent database backup?
For example, dropping a newly added column can permanently remove data collected after deployment.
A rollback script should be generated, reviewed, and tested before it is executed against production.
7. Create a Corrective Migration When Forward Recovery Is Safer
Sometimes the best recovery strategy is to move forward rather than reverse the database.
Suppose a production migration successfully added a column but failed while creating an index. After investigating, you confirm that the column is present and the migration is not recorded as applied.
You may need to reconcile the database state before proceeding. One option is to restore the database to its original state and retry a corrected migration. Another is to use a controlled repair script to complete the intended change and then carefully align the migration history.
If the failed migration has already been applied and recorded, create a new migration to correct the remaining issue.
For example:
dotnet ef migrations add AddMissingEmployeeIndex
Then define the missing operation:
public partial class AddMissingEmployeeIndex : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_Employees_Department",
table: "Employees",
column: "Department");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Employees_Department",
table: "Employees");
}
}
This approach is appropriate when the earlier migration is already considered successfully applied and the database needs a separate corrective change.
If the earlier migration was never recorded, do not simply add a later migration and assume EF Core will skip the failed one. The migration sequence and database state must be reconciled first.
8. Use SQL Scripts for Safer Production Recovery
Applying migrations directly from application startup or running database update commands manually can make production recovery harder to control.
A better approach for many production systems is to generate SQL scripts and review them before execution.
Generate a script for the migration range:
dotnet ef migrations script PreviousMigration LatestMigration --output migration.sql
For environments where databases may have different migration histories, generate an idempotent script:
dotnet ef migrations script --idempotent --output migration.sql
An idempotent script checks the migration history table and applies migrations that are not recorded as applied.
However, idempotency does not mean the script can repair every partially modified schema. If a failed migration left an object behind without a corresponding history record, the script may still fail when it tries to create that object again.
Always inspect the generated SQL and verify the database's actual state before running it.
For automated deployments, EF Core also supports migration bundles:
dotnet ef migrations bundle --output artifacts/efbundle
A migration bundle can be generated during the build and executed as a separate deployment step. It does not require the .NET SDK or EF Core tools to be installed on the production server.
Choose SQL scripts when database administrators need to review and approve the exact SQL. Choose migration bundles when you want a packaged migration executable in an automated deployment workflow.
9. Prevent Migration Failures in Future Deployments
Recovery is important, but preventing migration problems is even better.
Use these practices in your deployment process:
Review generated migrations before applying them to production.
Test migrations against a database with realistic data volume.
Take a backup or verify point-in-time recovery before high-risk schema changes.
Use a dedicated deployment identity with schema modification permissions.
Keep the application's runtime database identity restricted to the permissions it needs.
Apply migrations through a controlled deployment job rather than having every application instance execute them independently.
Monitor migration execution and retain the complete error logs.
Validate application compatibility with the old and new database schemas.
Test rollback scripts separately, especially when they remove columns or transform data.
Avoid combining unrelated schema changes into one large migration.
For applications using multiple instances, coordinate migration execution through a deployment process. EF Core 9 and later include migration locking for supported migration execution paths, but this does not replace deployment coordination or database compatibility planning.
10. Common Mistakes During Production Recovery
A few recovery shortcuts can turn a migration failure into a much bigger production issue.
Mistake |
Why it is risky |
Better approach |
Deleting a row from __EFMigrationsHistory immediately |
EF Core may try to repeat operations that already exist |
Inspect the schema and history together |
Rerunning a failed migration without investigation |
Existing columns, indexes, or constraints may cause another failure |
Identify which operations completed |
Running Down without checking its impact |
Rollback operations can delete production data |
Review and test rollback SQL |
Editing an already deployed migration |
Existing databases may have applied an earlier version of that migration |
Use a corrective migration when appropriate |
Applying migrations from every application instance |
Concurrent deployment activity can cause conflicts |
Use a coordinated migration job |
Assuming an idempotent script repairs partial changes |
It checks migration history, not every database object |
Validate the schema before execution |
The most important principle is to treat the migration history table and the actual database schema as two separate pieces of evidence. Both must be consistent before deployment can safely continue.
Summary
A failed EF Core migration in production does not always mean the database has been rolled back to its original state. Transaction behavior, database provider limitations, and non-transactional operations can leave different outcomes.
Start by stopping additional migration attempts, reviewing the error, inspecting the migration history, and comparing the actual database schema with the intended changes. Depending on the result, you may retry a corrected migration, restore the database, apply a controlled repair, or create a corrective migration.
For future deployments, generate and review SQL scripts or use migration bundles, maintain reliable backups, and coordinate migration execution through a dedicated deployment process.
A safe recovery is not simply about getting the migration to run again. It is about restoring consistency between the database schema, migration history, and application code without losing production data.
EF Core migrationsEntity Framework Coreproduction database migrationsmigration recoverydatabase schema__EFMigrationsHistorySQL Server
Join the conversation! Your thoughts help the community grow.