EF Core Migrations: Managing Schema Evolution in C
Entity Framework Core (EF Core) migrations provide a structured, version-controlled mechanism for updating a relational database schema to keep it synchronized with your application's C# domain models. As business requirements change, your data models inevitably shift, requiring new tables, columns, or modified relationships. This guide explains the core concepts of EF Core migrations, demonstrates how to generate and apply schema changes, and covers best practices for managing database evolution safely across development and production environments.
What Are EF Core Migrations?
In EF Core, migrations bridge the gap between your C# entity classes
and the underlying relational database structure. Instead of manually
writing and maintaining separate SQL DDL (Data Definition Language)
scripts, EF Core inspects your DbContext and model
configurations, compares them against a snapshot of your previous
schema, and automatically generates code to reconcile the
differences.
Every migration consists of three primary elements:
- The Migration File: Contains an
Upmethod (applying the changes) and aDownmethod (reverting the changes) using EF Core's fluent migration API. - The Metadata File: Stores internal EF Core information used during operations.
- The Model Snapshot: A C# representation of your current overall database schema, which EF Core references when calculating the next migration.
The Migration Lifecycle
Database evolution using EF Core follows a predictable cycle:
- Modify C# Models: Update, add, or remove entity
classes, properties, or Fluent API configurations in your
DbContext. - Add a Migration: EF Core computes the difference between the updated models and the existing snapshot, generating a new migration file.
- Review the Migration: Developers inspect the
generated
UpandDownmethods to verify operations, especially for destructive changes such as dropped columns or renamed properties. - Apply to Database: The migration updates the
database schema and logs the applied migration name in a tracking table
named
__EFMigrationsHistory.
Essential Commands for Schema Management
You can execute migrations using either the .NET CLI or the Visual Studio Package Manager Console (PMC).
Adding a Migration
When you modify an entity, generate a migration by providing a descriptive name:
.NET CLI:
dotnet ef migrations add AddUserProfileTablePackage Manager Console:
Add-Migration AddUserProfileTable
Applying Migrations to the Database
To apply all pending migrations directly to your target database:
.NET CLI:
dotnet ef database updatePackage Manager Console:
Update-Database
Reverting Changes
To roll back a database to a specific earlier migration, target that migration name:
.NET CLI:
dotnet ef database update PreviousMigrationName
To remove the last generated migration file (only if it has not yet been applied to the database):
.NET CLI:
dotnet ef migrations remove
Handling Complex Schema Evolution
Not all schema evolutions are straightforward column additions. Real-world evolution requires handling edge cases deliberately:
- Renaming Properties: By default, EF Core may
interpret a renamed property as dropping the old column and creating a
new one, causing data loss. You can explicitly customize the generated
migration using the
migrationBuilder.RenameColumn()method instead. - Custom SQL Execution: When a schema change requires
data transformation (such as populating a new column with transformed
data from an existing one), you can inject raw SQL directly into the
migration using
migrationBuilder.Sql("UPDATE Users SET ...").
Deploying Migrations to Production
While running context.Database.Migrate() at application
startup is convenient for local development, it is discouraged in
production environments. Startup execution can cause concurrency
deadlocks if multiple application instances start simultaneously and
risks deploying unreviewed changes to live data.
The recommended approaches for production schema evolution are:
SQL Script Generation: Generate idempotent SQL scripts from your migrations using CI/CD pipelines:
dotnet ef migrations script --idempotent --output migrate.sqlAn idempotent script uses conditional logic (checking the
__EFMigrationsHistorytable) so it can safely execute against databases at any revision level.Migration Bundles: Generate a standalone, self-contained executable that applies migrations:
dotnet ef migrations bundleMigration bundles run directly inside deployment pipelines without requiring the entire .NET SDK on the target execution environment.