| name | dotnet-ef-migrations |
| description | Master Entity Framework Core migrations with code-first approach, migration strategies, data seeding, rollback procedures, and production deployment patterns. Use when managing database schema changes in .NET applications. |
.NET Entity Framework Core Migrations
Master EF Core migrations for .NET 8+ with code-first database management.
Setup
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
DbContext Configuration
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
modelBuilder.Entity<Order>().HasQueryFilter(o => !o.IsDeleted);
base.OnModelCreating(modelBuilder);
}
}
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
builder.HasKey(o => o.Id);
builder.Property(o => o.OrderNumber)
.IsRequired()
.HasMaxLength(20);
builder.Property(o => o.TotalAmount)
.HasColumnType("decimal(18,2)");
builder.HasIndex(o => o.OrderNumber)
.IsUnique();
builder.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasMany(o => o.Items)
.WithOne(i => i.Order)
.HasForeignKey(i => i.OrderId)
.OnDelete(DeleteBehavior.Cascade);
}
}
Migration Commands
dotnet ef migrations add InitialCreate
dotnet ef migrations add AddOrderStatus
dotnet ef migrations add UpdateCustomerEmail
dotnet ef database update
dotnet ef database update AddOrderStatus
dotnet ef database update PreviousMigration
dotnet ef migrations remove
dotnet ef migrations script
dotnet ef migrations script InitialCreate AddOrderStatus
dotnet ef migrations script --idempotent
dotnet ef migrations list
dotnet ef database drop --force
Migration File Structure
public partial class AddOrderStatus : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Status",
table: "Orders",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_Orders_Status",
table: "Orders",
column: "Status");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Orders_Status",
table: "Orders");
migrationBuilder.DropColumn(
name: "Status",
table: "Orders");
}
}
Data Seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrderStatus>().HasData(
new OrderStatus { Id = 1, Name = "Pending" },
new OrderStatus { Id = 2, Name = "Processing" },
new OrderStatus { Id = 3, Name = "Shipped" },
new OrderStatus { Id = 4, Name = "Delivered" }
);
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "OrderStatuses",
columns: new[] { "Id", "Name" },
values: new object[,]
{
{ 1, "Pending" },
{ 2, "Processing" },
{ 3, "Shipped" },
{ 4, "Delivered" }
});
}
Custom SQL in Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
CREATE INDEX IX_Orders_CreatedAt_Status
ON Orders (CreatedAt, Status)
WHERE IsDeleted = 0
");
migrationBuilder.Sql(@"
CREATE PROCEDURE GetOrdersByCustomer
@CustomerId uniqueidentifier
AS
BEGIN
SELECT * FROM Orders
WHERE CustomerId = @CustomerId
AND IsDeleted = 0
END
");
migrationBuilder.Sql(@"
CREATE VIEW vw_ActiveOrders AS
SELECT o.*, c.Name AS CustomerName
FROM Orders o
INNER JOIN Customers c ON o.CustomerId = c.Id
WHERE o.IsDeleted = 0
");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DROP VIEW IF EXISTS vw_ActiveOrders");
migrationBuilder.Sql("DROP PROCEDURE IF EXISTS GetOrdersByCustomer");
migrationBuilder.Sql("DROP INDEX IF EXISTS IX_Orders_CreatedAt_Status ON Orders");
}
Production Deployment
if (app.Environment.IsDevelopment())
{
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
}
Migration Strategies
1. Script-Based (Recommended for Production)
dotnet ef migrations script --idempotent --output migration.sql
2. Runtime Migration
await dbContext.Database.MigrateAsync();
3. Migration Bundles
dotnet ef migrations bundle --self-contained -r linux-x64
./efbundle --connection "Server=prod;Database=MyDb;..."
Handling Migration Conflicts
git pull origin main
dotnet ef migrations remove
dotnet ef migrations add YourFeature
dotnet ef database update
Rolling Back Migrations
dotnet ef database update PreviousMigration
dotnet ef database update 0
dotnet ef migrations script CurrentMigration PreviousMigration --output rollback.sql
Best Practices
- Never Modify Applied Migrations - Create new migration instead
- Review Generated Migrations - Check SQL before applying
- Test Migrations - Test on copy of production data
- Use Transactions - Migrations run in transactions by default
- Backup Before Migration - Always backup production database
- Idempotent Scripts - Use
--idempotent flag for production
- Version Control - Commit migrations with code changes
- Data Migration - Separate data migrations from schema migrations
Common Patterns
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Email",
table: "Customers",
nullable: true);
migrationBuilder.Sql("UPDATE Customers SET Email = 'unknown@example.com' WHERE Email IS NULL");
migrationBuilder.AlterColumn<string>(
name: "Email",
table: "Customers",
nullable: false);
}
migrationBuilder.RenameColumn(
name: "OldName",
table: "TableName",
newName: "NewName");
migrationBuilder.AlterColumn<decimal>(
name: "Price",
table: "Products",
type: "decimal(18,2)",
nullable: false,
oldClrType: typeof(float));