Isaac.

EF Core Migrations

Version control for your database schema with Entity Framework Core.

By EMEPublished: February 20, 2025
entity frameworkmigrationsdatabase schemaaspnet core

A Simple Analogy

Migrations are like version control for databases. Each change creates a new version file, you can move forward or backward in time, and team members sync the same schema.


Why Migrations?

  • Version control: Track all schema changes in Git
  • Team coordination: Everyone applies same changes
  • Production deployment: Reliable schema updates
  • Rollback capability: Undo changes if needed
  • Documentation: Migration history explains why

Create Migration

// Define entities
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("connection-string");
    }
}

// Create migration
// dotnet ef migrations add InitialCreate

// Migration file generated automatically:
// Migrations/20250220120000_InitialCreate.cs

Migration Structure

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Users",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Name = table.Column<string>(nullable: false),
                Email = table.Column<string>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Users", x => x.Id);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "Users");
    }
}

Applying Migrations

// In Program.cs
var app = builder.Build();

// Auto-apply migrations on startup
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.Migrate();  // Applies all pending migrations
}

app.Run();
# Manual application
dotnet ef database update

# Apply specific migration
dotnet ef database update InitialCreate

# Rollback to previous
dotnet ef database update PreviousMigration

# List migrations
dotnet ef migrations list

Adding Columns

// Add column to entity
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; } // NEW
}

// Create migration
// dotnet ef migrations add AddCreatedAtToUsers

// Generated migration:
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AddColumn<DateTime>(
        name: "CreatedAt",
        table: "Users",
        nullable: false,
        defaultValue: DateTime.UtcNow);
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropColumn("CreatedAt", "Users");
}

Complex Migrations

public partial class SplitNameColumn : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        // Add new columns
        migrationBuilder.AddColumn<string>(
            name: "FirstName",
            table: "Users",
            nullable: true);

        migrationBuilder.AddColumn<string>(
            name: "LastName",
            table: "Users",
            nullable: true);

        // SQL to split existing name
        migrationBuilder.Sql(
            @"UPDATE Users SET FirstName = SUBSTRING(Name, 1, CHARINDEX(' ', Name)-1),
                             LastName = SUBSTRING(Name, CHARINDEX(' ', Name)+1, LEN(Name))");

        // Make new columns non-nullable
        migrationBuilder.AlterColumn<string>(
            name: "FirstName",
            table: "Users",
            nullable: false);

        migrationBuilder.AlterColumn<string>(
            name: "LastName",
            table: "Users",
            nullable: false);

        // Drop old column
        migrationBuilder.DropColumn("Name", "Users");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        // Reverse operations...
    }
}

Best Practices

  1. Create frequently: One feature = one migration
  2. Name descriptively: AddUserRoleColumn, not Migration2
  3. Keep idempotent: Safe to run multiple times
  4. Test rollbacks: Verify Down() works
  5. Review generated code: Ensure correctness

Related Concepts

  • Database snapshots
  • Initial data seeding
  • Shadow properties
  • Fluent API configuration

Summary

EF Core Migrations provide version control for database schemas. Master creating, applying, and rolling back migrations to maintain consistency across development and production environments.