Ef core migration safety
Skill Sarmkadan/dotnet-senior-skills/skills/ef-core-migration-safety
Review EF Core migrations for data loss, downtime, and deploy-order hazards. Use when adding, reviewing, or applying EF Core migrations, or when a schema change must ship without downtime.From its SKILL.md
npx -y skills add Sarmkadan/dotnet-senior-skills --skill ef-core-migration-safetyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
3.9 KB, 812 tokens by cl100k_base, as published. Nobody here has run it
EF Core Migration Safety
Classify every migration before merging
- Additive online - new nullable column, new table, new index (if built concurrently). Safe to deploy in any order.
- Additive blocking - new NOT NULL column without default on a large table, new index without
CONCURRENTLY(Postgres) /ONLINE = ON(SQL Server). Locks the table for the duration. - Destructive - drop column/table, narrow a type, add a constraint existing rows violate, rename. Requires the expand/contract pattern below.
Grep the generated migration for DropColumn, DropTable, AlterColumn, RenameColumn, AddColumn with nullable: false. Any hit means the migration cannot be reviewed by skimming the model diff - read the SQL via dotnet ef migrations script.
Renames are drops in disguise
EF cannot always tell a rename from drop+add. Verify the migration contains RenameColumn, not this:
// non-compiling: illustrative
// WRONG: silently destroys data
migrationBuilder.DropColumn(name: "Surname", table: "Users");
migrationBuilder.AddColumn<string>(name: "LastName", table: "Users");
If the scaffolder produced drop+add, hand-edit it to RenameColumn. Test by applying to a database with data, not an empty one.
Zero-downtime column changes (expand/contract)
Old app code and new schema coexist during a rolling deploy. Never ship a migration the previous app version cannot run against.
Adding a required column:
// Release 1: add nullable, app writes it
migrationBuilder.AddColumn<string>("Region", "Orders", nullable: true);
// Release 2: backfill out-of-band, then enforce
migrationBuilder.Sql("UPDATE Orders SET Region = 'EU' WHERE Region IS NULL");
migrationBuilder.AlterColumn<string>("Region", "Orders", nullable: false);
Dropping a column: release 1 removes every read and write of the property but keeps it mapped, so old and new app versions both run against the existing column; release 2 removes the property from the model and ships the DropColumn migration. A one-release drop breaks the still-running old instances.
NOT NULL with defaultValue on big tables
migrationBuilder.AddColumn<bool>("IsActive", "Users", nullable: false, defaultValue: true);
On SQL Server 2012+/Postgres 11+ this is metadata-only. On older engines it rewrites the table under an exclusive lock. Know your target before approving.
The shadow-property trap
An FK without a navigation-configured principal or a misspelled property gets a shadow column (CustomerId1). Symptoms: a duplicate-looking column in the migration, silently null FKs at runtime. When a migration adds a column you did not add to the model, stop - it is almost always a relationship EF inferred wrongly. Fix the model configuration; never merge the migration hoping it is harmless.
Operational rules
- Never call
Database.Migrate()from application startup in multi-instance deployments: concurrent migrators race, and a failed migration takes the app down. Run migrations as a separate deploy step (dotnet ef database updateor an idempotent script with--idempotent). - Generated
Down()methods restore schema, not data. Treat rollback of destructive migrations as impossible; roll forward instead. - Migrations are immutable once merged to main. Fix mistakes with a new migration, never by editing an applied one - the
__EFMigrationsHistoryhash will not match and other environments diverge. - Squash only migrations that no environment has partially applied.
- Review raw SQL in migrations (
migrationBuilder.Sql) for idempotency: it runs exactly once per database, but--idempotentscripts may replay surrounding context.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.