Isaac.

database

EF Core Migrations

Version control for your database schema with Entity Framework Core.

By Emem IsaacDecember 1, 20223 min read
#entity framework#migrations#database schema#aspnet core
Share:

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.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours