Migrations

Migrations turn a model change into a versioned, reviewable schema change you can apply to a database and roll back.

Syntaxdotnet ef migrations add AddProducts → dotnet ef database update

Your entity classes describe a schema. The database has an actual schema. A migration is the recorded difference between the two, stored as C# code in your project.

The loop

Install the tool once with dotnet tool install --global dotnet-ef, and add the Microsoft.EntityFrameworkCore.Design package to the project. Then change your classes and run dotnet ef migrations add AddProducts. EF Core compares your model against the snapshot of the previous model and writes three files into a Migrations folder: the migration, a designer file, and an updated snapshot. All three belong in source control.

dotnet ef database update applies every migration the database has not seen yet. EF Core records applied migrations in a table it owns, __EFMigrationsHistory, which is how it knows what is left to do.

Going backwards

Each migration has an Up method and a Down method. Up moves the schema forward, Down undoes it. dotnet ef database update AddProducts runs the Down of everything newer; dotnet ef database update 0 reverts all of them. dotnet ef migrations remove deletes a migration you have not applied yet.

Applying in production

For deployment, generate SQL with dotnet ef migrations script --idempotent so a human can read it, or a single-file bundle with dotnet ef migrations bundle. Calling Database.MigrateAsync() at startup takes a database-wide lock as of EF Core 9, so replicas no longer race each other, but the application then needs rights to change the schema, nobody reviews the SQL, and during a rolling deployment old replicas keep serving traffic against the new schema. Prefer a one-shot job before the new version starts.

Example

Example · csharp
// dotnet add package Microsoft.EntityFrameworkCore.Design
// dotnet ef migrations add AddProducts   writes the file below
// dotnet ef database update              runs Up against the database
using Microsoft.EntityFrameworkCore.Migrations;

Console.WriteLine("Apply this migration with: dotnet ef database update");

// The generated file is Migrations/20260214093000_AddProducts.cs and also
// carries [DbContext(typeof(ShopContext))] and [Migration("20260214093000_AddProducts")];
// the attributes are left off here so this file compiles on its own.
public partial class AddProducts : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "products",
            columns: table => new
            {
                Id = table.Column<int>(type: "INTEGER", nullable: false)
                    .Annotation("Sqlite:Autoincrement", true),
                Name = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_products", x => x.Id);
            });
    }

    // Down is the exact reverse of Up, and it is what a rollback runs.
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "products");
    }
}

// Output:
// Apply this migration with: dotnet ef database update

When to use it

  • Add a column to a live table without dropping the table or losing its rows.
  • Hand a DBA a reviewable SQL script for a release instead of letting the app change the schema.
  • Roll a bad schema change back to the previous migration while you fix the code.

More examples

The commands, in the order you run them

Keep this sequence next to you while learning. The idempotent script checks the history table itself, so it is safe to run against a database whose exact migration state you do not know.

Example · csharp
// Once per machine:
//   dotnet tool install --global dotnet-ef
// Once per project:
//   dotnet add package Microsoft.EntityFrameworkCore.Design

// After changing the entity classes:
//   dotnet ef migrations add AddSkuIndex
//   dotnet ef migrations list              shows which are applied
//   dotnet ef database update              applies them locally

// Made a mistake and have not applied it yet:
//   dotnet ef migrations remove

// For a deployment someone has to approve:
//   dotnet ef migrations script --idempotent --output artifacts/migrations.sql

// For an automated deployment, a single executable that needs no SDK:
//   dotnet ef migrations bundle --self-contained -r linux-x64 --output artifacts/efbundle
//   ./efbundle --connection "$DEPLOY_CONNECTION"

// In continuous integration, fail the build if someone changed the model
// but forgot the migration:
//   dotnet ef migrations has-pending-model-changes

Editing a generated migration to keep the data

EF Core compares two models and cannot see intent, so a rename looks like a deletion plus an addition. Read every generated migration before applying it, and fix this one by hand. Also fix Down, or the rollback loses the data instead.

Example · csharp
using Microsoft.EntityFrameworkCore.Migrations;

public partial class RenameTitleToName : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        // What EF Core generated, which would lose every value:
        //   migrationBuilder.DropColumn(name: "Title", table: "products");
        //   migrationBuilder.AddColumn<string>(name: "Name", table: "products", ...);

        // What to replace it with:
        migrationBuilder.RenameColumn(
            name: "Title",
            table: "products",
            newName: "Name");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "Name",
            table: "products",
            newName: "Title");
    }
}

Migrating at startup, and why to avoid it

The lock removes the multi-replica race, not the other costs: the running application needs schema permissions, no human sees the SQL, there is no rollback step, and a slow migration delays startup past a readiness probe. Keep it to development.

Example · csharp
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ShopContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("Shop")));

var app = builder.Build();

// Acceptable for a single-replica internal tool. Since EF Core 9 this takes a
// database-wide lock, so two replicas cannot corrupt each other, and it throws
// if the model has changes with no migration behind them.
if (app.Environment.IsDevelopment())
{
    using IServiceScope scope = app.Services.CreateScope();
    ShopContext db = scope.ServiceProvider.GetRequiredService<ShopContext>();
    await db.Database.MigrateAsync();
}

app.Run();

// In production, run the bundle as a job before the new version rolls out.
// Never call EnsureCreatedAsync before MigrateAsync: EnsureCreated builds the
// schema without recording any migration, and MigrateAsync then fails.

Discussion

  • Be the first to comment on this lesson.