As your application grows, your domain models change. You add properties, remove tables, and establish new relationships. Entity Framework Core Migrations provide a way to evolve your database schema synchronously with your C# code, ensuring you never have to manually write `ALTER TABLE` SQL scripts again.
Migrations are an organized way to apply incremental updates to a database schema. When you change a C# entity class, EF Core compares the new model to a snapshot of the old model. It generates a single Migration File containing C# instructions (Up and Down methods) representing the exact changes. You then apply those instructions to the database.
To use migrations, you need the EF Core CLI tools. Install them globally: dotnet tool install --global dotnet-ef.
After changing a class (e.g., adding an Age property to the User class), run:
# Syntax: dotnet ef migrations add {MigrationName}
dotnet ef migrations add AddUserAgeColumn
This creates two files in your project: a heavily timestamped file (e.g., 20261014_AddUserAgeColumn.cs) and an updated ApplicationDbContextModelSnapshot.cs.
Always review what EF Core generated. Sometimes it infers things incorrectly (like dropping a column instead of renaming it).
public partial class AddUserAgeColumn : Migration
{
// The UP method is applied when you UPDATE the database
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Age",
table: "Users",
type: "int",
nullable: false,
defaultValue: 0);
}
// The DOWN method is applied if you need to ROLLBACK the migration
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Age",
table: "Users");
}
}
# Applies all pending migrations to the database configured in appsettings.json
dotnet ef database update
Many tutorials tell you to put context.Database.Migrate() in Program.cs so the app updates the database automatically on startup. Do not do this in production. If you deploy a load-balanced app, three servers will start up simultaneously, and all three will attempt to run migrations concurrently, deadlocking your database and causing catastrophic failures.
In highly regulated environments, automated updates aren't allowed. A DBA must review everything. You generate an idempotent script (a script that checks if a migration already exists before running it).
dotnet ef migrations script --idempotent --output pipeline/deploy.sql
This script is reviewed by the DBA and executed manually or via a secure pipeline tool.
Introduced in EF Core 6, you can compile all migrations into a single, standalone executable that doesn't rely on the .NET SDK. Your Azure DevOps or GitHub Actions pipeline runs this bundle as a step before deploying the web code.
# 1. Build the bundle locally or in the CI agent
dotnet ef migrations bundle --self-contained -r linux-x64 -o efbundle
# 2. Run the bundle in the CD pipeline against the production DB
./efbundle --connection "Server=prod_db;Database=MyApp;User=admin;Password=secret;"
# Removes the last migration file and reverts the snapshot
dotnet ef migrations remove
# 1. Target the name of the PREVIOUS migration to rollback the database
dotnet ef database update InitialCreate
# 2. Now that the DB is rolled back, delete the bad migration code
dotnet ef migrations remove
AddEmailToCustomer, not Update3).main branch and deployed to Staging/Production, never modify its C# file. To fix a mistake, create a new mapping.FullName to Name, EF Core will drop the FullName column (losing all data!) and create a new Name column. You must edit the migration file manually to use migrationBuilder.RenameColumn().Q: "How does EF Core know which migrations have already been applied to a database, and how does it prevent running them twice?"
Architect Answer: "The first time a migration runs, EF Core creates a hidden tracking table in your database called __EFMigrationsHistory. When you execute an update, EF Core compares your compiled migration files against the rows in this table. If a migration's ID (which includes a timestamp) exists in the table, it is skipped. If it doesn't, EF runs the Up() method of that migration and then inserts a record into the history table. This ensures updates are incremental, ordered, and never duplicated."