Exercise 2: Migration Management
Objective
Practice creating, modifying, and managing migrations in a realistic development workflow.
Initial Setup
Start with a simple Product entity:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Tasks
Task 1: Initial Migration
- Create the initial migration:
dotnet ef migrations add InitialCreate
- Apply to database:
dotnet ef database update
- Verify the database was created
Task 2: Add Category Relationship
Modify entities:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}
- Create migration:
dotnet ef migrations add AddCategory
- Review the generated migration file
- Apply migration
- Rollback to previous migration:
dotnet ef database update InitialCreate
- Reapply:
dotnet ef database update
Task 3: Add Seed Data
Add seed data in OnModelCreating:
modelBuilder.Entity<Category>().HasData(
new Category { Id = 1, Name = "Electronics" },
new Category { Id = 2, Name = "Books" },
new Category { Id = 3, Name = "Clothing" }
);
modelBuilder.Entity<Product>().HasData(
new Product { Id = 1, Name = "Laptop", Price = 999.99m, CategoryId = 1 },
new Product { Id = 2, Name = "Mouse", Price = 29.99m, CategoryId = 1 },
new Product { Id = 3, Name = "C# Book", Price = 49.99m, CategoryId = 2 }
);
- Create migration:
dotnet ef migrations add SeedData
- Apply and verify seed data exists
Task 4: Add Stored Procedure
Create a migration that adds a stored procedure:
public partial class AddGetProductsByCategory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
CREATE PROCEDURE GetProductsByCategory
@CategoryId INT
AS
BEGIN
SELECT * FROM Products WHERE CategoryId = @CategoryId
END
");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DROP PROCEDURE IF EXISTS GetProductsByCategory");
}
}
Task 5: Rename Column (Safely)
Rename Price to UnitPrice without losing data:
- Add new column with migration
- Copy data from old to new column with SQL
- Remove old column in separate migration
Create the migrations to accomplish this.
Task 6: Generate SQL Scripts
# Generate script for all migrations
dotnet ef migrations script --output migrations.sql
# Generate idempotent script (safe to run multiple times)
dotnet ef migrations script --idempotent --output deploy.sql
# Generate script between two migrations
dotnet ef migrations script AddCategory AddGetProductsByCategory --output partial.sql
Review the generated SQL files.
Task 7: Handle Migration Conflict
Simulate a merge conflict:
- Create branch A and add migration
AddDescription - Switch to branch B and add migration
AddStock - Merge and resolve the migration timestamp conflict
Task 8: Production Deployment
- Generate production deployment script
- Review script for:
- Idempotent operations
- Transaction handling
- Rollback capability
- Test in staging environment
Validation Checklist
- All migrations created successfully
- Can rollback and reapply migrations
- Seed data inserted correctly
- Stored procedure works
- Column rename preserved data
- SQL scripts are idempotent
- Migration conflicts resolved
Common Issues
Issue: "The model backing the context has changed" Solution: Create a new migration
Issue: Migration already applied
Solution: Use dotnet ef migrations remove if not applied, or create a new reverting migration
Issue: Timestamp conflicts Solution: Remove one migration, pull changes, recreate
Best Practices Learned
- Never modify applied migrations
- Always review generated SQL before applying
- Use idempotent scripts for production
- Test rollback capability
- Keep migrations small and focused
- Don't mix schema and data changes
- Use transactions for complex changes
Bonus Challenges
- Create a migration that adds an index only if it doesn't exist
- Implement a migration that migrates data between tables
- Create a custom migration operation
- Set up automatic migration on application startup (development only)
- Create a migration that modifies existing data based on business rules