Database Migration Workflow in Entity Framework Core
When utilizing the Code-First approach in Entity Framework Core, the data model rarely remains static after the initial deployment. Adding properties to entities, altering relationships, or introducing new tables requires a systematic mechanism to synchronize these structural changes with the underlying database schema without data loss.
Prerequisites
Ensure the following NuGet packages are referenced in your data access layer project:
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.x" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.x" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.x" />
The Tools package is essential as it provides the Package Manager Console commands for scaffolding migrations.
Generating Migration Scripts
Open the Package Manager Console via Tools → NuGet Package Manager → Package Manager Console. Scaffold your initial migration by executing:
Add-Migration InitialSchema
This command creates a Migrations directory containing a migration class with Up() and Down() methods, alongside a model snapshot that tracks the current state of your entities.
For subsequent schema modifications—such as adding a ModifiedAt timestamp to an existing entity—generate a new migration with a descriptive identifier:
Add-Migration AddModifiedAtToProducts
Applying Schema Changes
To execute the generated SQL commands against the target database:
Update-Database
This applies all pending migrations sequentially and records their execution in the __EFMigrationsHistory system table.
To target a specific migration (useful for rollbacks during development):
Update-Database InitialSchema
Critical Conflict: EnsureCreated vs. Migration Pipeline
A frequent pitfall arises when prototyping with EnsureCreated() or EnsureCreatedAsync():
// Anti-pattern: Bypasses the migrations system
await dbContext.Database.EnsureCreatedAsync();
This method inspects the database existence and generates the schema directly from the current model state, bypassing the migrations infrastructure antirely. If you subsequently execute Update-Database against this database, the operation fails because the migration history tracking is absent, despite the physical tables already existing.
Typical error manifestations include:
SQLite Error 1: table "Products" already exists
-- or --
SqlException: There is already an object named 'Users' in the database.
Resolution Strategy
If the database was created via EnsureCreated() but you now require migrations:
-
Drop the existing database to eliminate the schema mismatch:
Drop-Database -
Remove all
EnsureCreated()orEnsureCreatedAsync()invocations from your startup logic. -
Execute the migration pipeline to recreate the database properly:
Update-Database
Additional Utility Commands
To remove the most recently scaffolded migration (only if not yet applied to the database):
Remove-Migration
To generate a SQL script representing the migrations without executing them (useful for production deployment reviews):
Script-Migration
To generate a script from a specific starting point to the latest:
Script-Migration InitialSchema AddModifiedAtToProducts
Note on Legacy Commmands
Unlike Entity Framework 6, EF Core does not utilize the Enable-Migrations command. The workflow relies exclusively on Add-Migration and Update-Database. If both EF6 and EF Core tool are installed, ensure you are using the EF Core-specific command set provided by Microsoft.EntityFrameworkCore.Tools.