← Back to Blog

The EF Core Migration That Would Have Dropped a Production Column

Renaming a property looks like a harmless refactor. To EF Core's migration scaffolding, it can look like deleting one column and creating an unrelated one — with every existing row's data going with it.

Pankaj Kumar
Senior Software Engineer — .NET, Blazor, ASP.NET Core
4+ years building production .NET and Blazor applications. Every DevToolsHub tool and article comes from real daily development work — not documentation summaries.
Published 15 Jul 2026· Last reviewed Jul 2026· 7 min read · About the author →
Senior .NET engineer working across ASP.NET Core APIs, EF Core, and Azure deployments.
Key takeaways
  • EF Core's migration scaffolder compares your current model against its last-known snapshot property by property — it has no concept of 'this is the same property, just renamed'
  • Renaming a C# property (or a table) without help generates a DropColumn + AddColumn pair, which is destructive: every existing value in that column is lost when the migration applies
  • The generated migration's Up() method is fully readable C# — DropColumn and AddColumn are as legible as RenameColumn, so the destructive version doesn't look obviously wrong at a glance
  • MigrationBuilder.RenameColumn preserves existing data and is a one-line manual edit to the generated migration once you know to look for it
  • A tool that explicitly flags DROP COLUMN operations in a migration script turns an easy-to-miss data-loss bug into an impossible-to-miss one
Table of Contents

The incident

Here's a common way this happens: a routine refactor renames a C# property on an entity — say CustomerName to ClientName — and a teammate runs dotnet ef migrations add to keep the database in sync. The generated migration compiles cleanly and reads like any other schema change, so it can pass review without a second look. It only gets caught if someone scrolls through the generated Up() method before it's applied and notices a DropColumn followed by an unrelated AddColumn, rather than the RenameColumn a simple rename should have produced — catching it before it ever reaches a database with real rows in that column.

Why a rename doesn't look like a rename to EF Core

When you run dotnet ef migrations add, EF Core compares your current DbContext model against a snapshot of what the model looked like the last time a migration was generated. That comparison happens property by property, matched by name. If you rename a C# property — say, CustomerName to ClientName — from EF Core's perspective, one property that used to exist is now gone, and a completely unrelated property with a different name has appeared. It has no way to infer that these are "the same column, just renamed," because nothing in the model tells it that. The result is a migration that drops the old column and adds a new one:

public partial class RenameCustomerNameToClientName : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(
            name: "CustomerName",
            table: "Orders");

        migrationBuilder.AddColumn<string>(
            name: "ClientName",
            table: "Orders",
            type: "nvarchar(max)",
            nullable: true);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(
            name: "ClientName",
            table: "Orders");

        migrationBuilder.AddColumn<string>(
            name: "CustomerName",
            table: "Orders",
            type: "nvarchar(max)",
            nullable: true);
    }
}

This migration is completely valid C#, compiles without warnings, and runs without error. It also deletes every existing value in CustomerName for every row in the Orders table the moment it applies — DROP COLUMN in SQL Server removes the column and its data outright; the subsequent ADD COLUMN creates a new, empty one that happens to share a table. There is no data migration step connecting the two.

Why this is easy to miss in code review

The generated migration file reads as ordinary, mechanical EF Core boilerplate — DropColumn followed by AddColumn is a completely normal-looking pair of calls, indistinguishable in shape from any other schema change. Unless a reviewer specifically recognizes that this particular pair represents a rename that lost its identity, and specifically checks whether the table already has production data that depends on the dropped column, the migration reads as unremarkable. The C# compiles, the SQL is syntactically ordinary, and nothing about the diff itself signals "this deletes data."

The fix: tell EF Core it's a rename, not two unrelated changes

Once you know the intent was a rename, the fix is a small manual edit to the generated migration, replacing the drop/add pair with MigrationBuilder.RenameColumn, which preserves every existing value:

protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "CustomerName",
            table: "Orders",
            newName: "ClientName");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "ClientName",
            table: "Orders",
            newName: "CustomerName");
    }

The same principle applies to renaming a table — DropTable + CreateTable generated by the scaffolder should become MigrationBuilder.RenameTable if the intent was a rename rather than a genuine drop-and-recreate. In both cases, EF Core's own scaffolding tool has no way to know your intent; it only knows what changed, not why, and it always assumes the more mechanical, more destructive interpretation by default.

The generalizable check: read every generated migration before it ships

Any migration containing DropColumn, DropTable, or DropForeignKey against a table that already holds production data deserves a specific, deliberate second look before it's allowed to run — not because these operations are always wrong, but because a genuine "remove this column, we no longer need the data" and an accidental "this was actually just a rename" produce byte-for-byte identical-looking migration code. The EF Core Migration SQL Explainer exists specifically to make that distinction visible without reading raw Up()/Down() C# line by line — paste a migration script and every destructive operation is called out explicitly, grouped by table, before it ever reaches a production database.

Try the free tool

EF Core Migration SQL Explainer

Paste a dotnet ef migrations script and get a grouped, plain-English breakdown of every table, column, index, and foreign key it touches — with destructive operations like DROP COLUMN flagged explicitly.

Open EF Core Migration SQL Explainer