# EF Core Migration Standards · EF-04

## Standard Metadata

- Area: ORM
- Owner: Backend Steering group
- Last reviewed: 2026-09-09
- Next review due from: 2026-12-01
- Enforcement: CodeAnalysis / Rosyln Analyser / code review
- Status: Active

## Migrations must be named descriptively and consistently · EF-04.1 · **MUST**

The name of each migration must be a clear indication of what a migration does, before opening the file. Always use PascalCase verb-noun names which describe the change(s) introduced in the migration.

### Good Example: Descriptive migration names

```text
20260521093000_AddColumnIsbnToBook.cs
20260521094500_CreateAuthorTable.cs
20260521100000_AddIndexOnOrderCreatedDate.cs
20260521340000_CreateStripePaymentConfigurationTable.cs
```

### Bad Example: Vague or inconsistent migration names

```text
20260521093000_AddData.cs
20260521094500_TC_changes.cs
20260521100000_Fix_bug_584223.cs
```

---

## Never edit a migration once it has been applied to a shared environment · EF-04.2 · **MUST**

Never edit existing migrations once they have already been applied to a deployed database. After the migration is applied it is recorded in the `__EFMigrationsHistory` table, and will never be reapplied.

### Good Example: Fix forward with a new migration

```bash
# Original migration AddBookIsbnColumn has already been deployed.
# The Isbn column needs a longer max length.

dotnet ef migrations add IncreaseBookIsbnLength
```

### Bad Example: Editing an applied migration

```cs
// Editing AddBookIsbnColumn.cs after it has already been applied
// to staging. The change will never run again on environments
// where the migration is already recorded as applied.
migrationBuilder.AddColumn(
    name: "Isbn",
    table: "Books",
    type: "nvarchar(20)", // was nvarchar(13)
    nullable: false);
```

---

## Keep migrations reversible · EF-04.3 · **MUST**

Every migration must define both `Up` and `Down` methods such that all changes can be rolled back when needed. Any operation in `Up` which cannot be expressed in `Down` is heavily discouraged.

### Good Example: Reversible migration

```cs
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AddColumn(
        name: "Isbn",
        table: "Books",
        type: "nvarchar(13)",
        nullable: false,
        defaultValue: "");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropColumn(
        name: "Isbn",
        table: "Books");
}
```

### Bad Example: Non-reversible migration

```cs
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql("ALTER TABLE Books ADD Isbn nvarchar(13) NOT NULL DEFAULT '';");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
}
```

---

## Keep migrations idempotent · EF-04.4 · **MUST**

Every migration must be able to be safely reapplied multiple times without adverse effects.

Note: This usually only applies to raw SQL inside migrations. Most `MigrationBuilder` commands are intrinsically guarded.

### Good Example: Guarded raw SQL

```cs
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql(@"
        IF NOT EXISTS (SELECT 1 FROM [Genres] WHERE [Name] = N'Fiction')
            INSERT INTO [Genres] ([Name]) VALUES (N'Fiction');
    ");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql("DELETE FROM [Genres] WHERE [Name] = N'Fiction';");
}
```

### Bad Example: Unguarded insert (clones data on every run)

```cs
protected override void Up(MigrationBuilder migrationBuilder)
{
    // Re-running inserts another 'Fiction' row each time — duplicates accumulate.
    migrationBuilder.Sql("INSERT INTO [Genres] ([Name]) VALUES (N'Fiction');");
}

protected override void Down(MigrationBuilder migrationBuilder)
{
}
```

---

## Each migration should contain one logical change · EF-04.5 · **SHOULD**

Every migration must be able to be reviewed, applied and rolled back on its own. Changes cannot be reverted independently when they are contained within a migration responsible for executing other, unrelated changes.

### Good Example: One change per migration

```bash
dotnet ef migrations add AddBookIsbnColumn
dotnet ef migrations add AddIndexOnOrderCreatedDate
dotnet ef migrations add RenameAuthorBioToDescription
```

### Bad Example: Multiple unrelated changes in one migration

```cs
// Migration: MiscChanges
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AddColumn(name: "Isbn", table: "Books", /* ... */);

    migrationBuilder.CreateIndex(name: "IX_Orders_CreatedDate", table: "Orders", /* ... */);

    migrationBuilder.RenameColumn(name: "Bio", table: "Authors", newName: "Description");
}
```
