
Understanding EF Core Migrations
Entity Framework Core migrations provide a structured way to evolve a database schema alongside application code. Rather than manually writing SQL scripts for every schema change, EF Core generates migration files that describe the required updates. These migrations can then be applied to development, staging, and production databases in a controlled manner.
While migrations are convenient during development, production environments introduce additional risks. A poorly planned migration can lock tables, cause application failures, or leave the database in an inconsistent state. Production databases often contain large amounts of data, active users, and integrations with other systems, meaning even small schema changes require careful planning.
Why Production Migrations Go Wrong
Many migration problems happen because developers test only against small local databases. A migration that runs in seconds locally may take several minutes against a production database with millions of rows. During this time, tables may be locked and the application may become unresponsive.
Another common issue is deploying application code before the database schema is ready. If the application expects a new column that does not yet exist, requests may fail immediately after deployment. The reverse is also dangerous because removing a column before all application instances stop using it can break older running versions.
Automatic migrations during application startup are another frequent source of production issues. While convenient for development, allowing applications to update production databases automatically removes deployment control and increases the risk of accidental schema changes.
Generating Safe Migrations
The first step towards safer deployments is understanding what EF Core actually generates. Every migration should be reviewed before it reaches production. Developers should never assume generated SQL is automatically optimal or safe.
A migration can be created using the standard command:
dotnet ef migrations add AddCustomerPhoneNumber
After generating the migration, review both the Up and Down methods carefully. Pay particular attention to operations such as dropping columns, renaming tables, or altering data types because these can result in data loss or long-running operations.
When possible, avoid destructive changes in a single release. Instead of immediately deleting a column, introduce the replacement first, migrate the data, update the application to use the new structure, and only remove the old column in a later deployment.
Using SQL Scripts Instead of Direct Updates
Applying migrations directly using dotnet ef database update may be acceptable in development, but production environments benefit from generated SQL scripts. Scripts can be reviewed, tested, and approved before execution.
EF Core can generate a migration script with:
dotnet ef migrations script
For deployment pipelines, it is usually better to generate idempotent scripts:
dotnet ef migrations script --idempotent
An idempotent script checks which migrations have already been applied before executing changes. This makes deployments more reliable across different environments and avoids duplicate execution.
SQL scripts also allow database administrators to review execution plans and identify potentially dangerous operations before they affect live systems.
Avoiding Breaking Changes
Production-safe migrations often rely on backward compatibility. Database changes should support both the old and new application versions during deployment.
For example, adding a nullable column is usually safe because older application versions simply ignore it. Renaming or removing a column is far riskier because running instances of the old application may still depend on it.
A safer deployment pattern involves several stages. First, add new schema elements without removing existing ones. Next, deploy application changes that support both old and new structures. Then migrate existing data if required. Finally, after verifying the new version is stable, remove obsolete structures in a later release.
This gradual approach reduces the risk of deployment failures and provides easier rollback options if problems occur.
Handling Large Tables
Large production tables require special attention. Adding indexes, updating data, or altering column types can become expensive operations that lock tables for extended periods.
Before running migrations in production, test execution times against realistic data volumes. A migration that updates every row in a table may need batching rather than a single statement.
Indexes should also be planned carefully. Creating a large index during peak traffic can severely impact performance. In some cases, it may be preferable to create indexes during maintenance windows or use online index operations if supported by the database platform.
Monitoring is equally important. Database CPU usage, locking behaviour, and query performance should all be observed during deployments so issues can be detected quickly.
Migration Testing Strategies
Production migrations should always be tested in staging environments that closely resemble production. Testing only the application is not enough because schema updates may behave differently with real data sizes and workloads.
A good staging environment should include realistic data volumes, similar hardware performance, and the same database engine version as production. This helps identify long-running operations, locking issues, and compatibility problems before deployment day.
Automated integration tests can also validate migrations. Tests should confirm that the schema updates correctly and that both old and new application behaviour works as expected after migration.
Rollback testing is often overlooked. Teams should verify not only that migrations succeed, but also that recovery plans work if something goes wrong.
Rollback and Recovery Planning
Not every migration can be safely rolled back automatically. Some schema changes permanently alter or remove data, making reversal impossible without backups.
Before production deployments, ensure database backups are recent and recovery procedures are understood. If a migration fails halfway through, restoring from backup may be the safest option.
EF Core supports migration rollback commands:
dotnet ef database update PreviousMigrationName
However, developers should not rely entirely on automatic rollback behaviour. Manual intervention may still be necessary for complex data transformations or partially completed operations.
A strong deployment strategy includes clear rollback steps, backup verification, and communication plans in case recovery is required.
Managing Migrations in CI/CD Pipelines
Modern deployment pipelines should treat database migrations as a first-class deployment concern. Migrations should be version-controlled alongside application code and executed as part of the release process.
Many teams separate migration execution from application deployment entirely. This allows schema changes to be validated before application instances begin using them.
Continuous integration pipelines can also validate migration quality automatically by generating SQL scripts, checking for destructive operations, and running migration tests against temporary databases.
Approval gates are valuable for production systems because they ensure risky migrations receive manual review before execution.
Final Thoughts
EF Core migrations simplify database development, but production systems require more discipline than local development environments. Safe migrations depend on planning, testing, backward compatibility, and controlled deployment processes.
Developers should avoid automatic production migrations, review generated SQL carefully, and always consider the impact of schema changes on running systems. By treating database deployments with the same care as application releases, teams can evolve production databases safely while minimising downtime and deployment risk.
Become a member
Get the latest news right in your inbox. It's free and you can unsubscribe at any time. We hate spam as much as we do, so we never spam!
Read next
Handling Soft Deletes in EF Core
When working with business applications, permanently deleting data is often undesirable. Records may need to be restored, audited or retained for compliance purposes. Soft deletes provide a simple solution by marking records as deleted rather than removing them from the database. In this article, we'll explore how to implement soft deletes in Entity Framework Core using query filters and save interception techniques.
Tracking vs No-Tracking Queries Explained
Entity Framework Core Performance Tips
