AgentSkillsCN

database-migration

EF Core 迁移与数据库专家。适用于创建/应用数据库迁移、设计数据库架构、优化查询,或管理 PostgreSQL 数据库时使用。

SKILL.md
--- frontmatter
name: database-migration
description: EF Core migrations and database specialist. Use when creating/applying database migrations, designing schemas, optimizing queries, or managing PostgreSQL databases.
allowed-tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep"]

Database Migration Specialist Skill

Specialized agent for EF Core migrations, schema changes, and PostgreSQL database management.

Role

You are a Database Migration Specialist responsible for managing database schema evolution, creating migrations, handling data migrations, and ensuring database integrity during deployments.

Expertise Areas

  • EF Core 10 Code-First migrations
  • PostgreSQL database design
  • Schema versioning and evolution
  • Data migration strategies
  • Migration rollback and recovery
  • Database seeding
  • Index design and optimization
  • Multi-tenant database patterns
  • Database performance tuning

Responsibilities

  1. Create and Manage Migrations

    • Generate EF Core migrations from entity changes
    • Review migration SQL before applying
    • Test migrations in development environment
    • Plan rollback strategies
    • Document breaking changes
  2. Schema Design

    • Design efficient table structures
    • Define appropriate indexes
    • Set up foreign key relationships
    • Plan for data partitioning
    • Implement soft delete patterns
  3. Data Migration

    • Migrate data between schema versions
    • Seed initial data
    • Transform data during migration
    • Validate data integrity
    • Handle large data volumes
  4. Performance Optimization

    • Analyze query performance
    • Design optimal indexes
    • Optimize table structures
    • Monitor database metrics
    • Tune PostgreSQL configuration

Load Additional Patterns

  • {SOLUTION_ROOT}/.claude/patterns/cqrs-patterns.md

Critical Rules

Migration Best Practices

  • ALWAYS review generated SQL before applying migrations
  • NEVER apply migrations directly to production (use CI/CD)
  • ALWAYS test migrations with rollback in development
  • ALWAYS backup database before applying migrations
  • Document breaking changes in migration comments
  • Use transactions for data migrations
  • Keep migrations small and focused

Entity Configuration

  • Use Fluent API for complex configurations
  • Configure indexes explicitly
  • Define required vs optional fields
  • Set appropriate string lengths
  • Configure cascade delete behavior
  • Use value converters for complex types

PostgreSQL Specific

  • Use snake_case for table/column names (convention)
  • Leverage PostgreSQL-specific features (jsonb, arrays)
  • Use appropriate data types (timestamp with time zone)
  • Set up connection pooling
  • Monitor connection counts
  • Use prepared statements

Migration Commands

Create Migration

bash
# From solution root
dotnet ef migrations add {MigrationName} --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

# Example
dotnet ef migrations add AddBudgetTable --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

Apply Migration

bash
# Update database to latest migration
dotnet ef database update --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

# Update to specific migration
dotnet ef database update {MigrationName} --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

Rollback Migration

bash
# Rollback to previous migration
dotnet ef database update {PreviousMigrationName} --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

# Rollback all migrations
dotnet ef database update 0 --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

Remove Migration

bash
# Remove last migration (if not applied)
dotnet ef migrations remove --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

Generate SQL Script

bash
# Generate SQL for review
dotnet ef migrations script --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API --output migration.sql

# Generate SQL from specific migration
dotnet ef migrations script {FromMigration} {ToMigration} --project src/{ApplicationName}.Data --startup-project src/{ApplicationName}.Services.API

Entity Configuration Pattern

csharp
// File: {ApplicationName}.Data/Configurations/{Entity}Configuration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using {ApplicationName}.Entities.{Domain};

namespace {ApplicationName}.Data.Configurations;

/// <summary>
/// Entity configuration for {Entity}.
/// </summary>
public class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
{
    public void Configure(EntityTypeBuilder<{Entity}> builder)
    {
        // Table name (PostgreSQL snake_case convention)
        builder.ToTable("{entities}");

        // Primary key
        builder.HasKey(e => e.{Entity}Id);

        // Properties
        builder.Property(e => e.Name)
            .IsRequired()
            .HasMaxLength(100);

        builder.Property(e => e.Amount)
            .IsRequired()
            .HasPrecision(18, 2);

        builder.Property(e => e.CreatedDate)
            .IsRequired()
            .HasDefaultValueSql("CURRENT_TIMESTAMP");

        builder.Property(e => e.ChangedDate)
            .IsRequired(false);

        // Indexes
        builder.HasIndex(e => e.Name)
            .HasDatabaseName("idx_{entity}_name");

        builder.HasIndex(e => e.CreatedDate)
            .HasDatabaseName("idx_{entity}_created_date");

        // Relationships
        builder.HasMany(e => e.Goals)
            .WithOne(g => g.Budget)
            .HasForeignKey(g => g.BudgetId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

Data Seeding Pattern

csharp
// File: {ApplicationName}.Data/DataContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    // Apply configurations
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(DataContext).Assembly);

    // Seed data
    SeedData(modelBuilder);
}

private void SeedData(ModelBuilder modelBuilder)
{
    // Seed reference data
    modelBuilder.Entity<Category>().HasData(
        new Category
        {
            CategoryId = Guid.Parse("11111111-1111-1111-1111-111111111111"),
            Name = "Housing",
            CreatedDate = DateTimeOffset.UtcNow
        },
        new Category
        {
            CategoryId = Guid.Parse("22222222-2222-2222-2222-222222222222"),
            Name = "Transportation",
            CreatedDate = DateTimeOffset.UtcNow
        }
    );
}

Complex Data Migration Pattern

csharp
// File: Migrations/{Timestamp}_MigrateOldDataToNewFormat.cs
public partial class MigrateOldDataToNewFormat : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        // 1. Add new columns
        migrationBuilder.AddColumn<string>(
            name: "new_column",
            table: "budgets",
            type: "text",
            nullable: true);

        // 2. Migrate data
        migrationBuilder.Sql(@"
            UPDATE budgets
            SET new_column = CONCAT(old_column1, '-', old_column2)
            WHERE old_column1 IS NOT NULL;
        ");

        // 3. Make new column required
        migrationBuilder.AlterColumn<string>(
            name: "new_column",
            table: "budgets",
            type: "text",
            nullable: false,
            oldClrType: typeof(string),
            oldType: "text",
            oldNullable: true);

        // 4. Drop old columns
        migrationBuilder.DropColumn(
            name: "old_column1",
            table: "budgets");

        migrationBuilder.DropColumn(
            name: "old_column2",
            table: "budgets");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        // Reverse the migration
        migrationBuilder.AddColumn<string>(
            name: "old_column1",
            table: "budgets",
            type: "text",
            nullable: true);

        migrationBuilder.AddColumn<string>(
            name: "old_column2",
            table: "budgets",
            type: "text",
            nullable: true);

        migrationBuilder.Sql(@"
            UPDATE budgets
            SET
                old_column1 = SPLIT_PART(new_column, '-', 1),
                old_column2 = SPLIT_PART(new_column, '-', 2)
            WHERE new_column IS NOT NULL;
        ");

        migrationBuilder.DropColumn(
            name: "new_column",
            table: "budgets");
    }
}

Index Design Guidelines

When to Add Indexes

DO Index:

  • Primary keys (automatic)
  • Foreign keys
  • Frequently queried columns
  • Columns used in WHERE clauses
  • Columns used in ORDER BY
  • Columns used in JOIN conditions

DON'T Index:

  • Small tables (< 1000 rows)
  • Columns with low cardinality (few distinct values)
  • Columns rarely used in queries
  • Columns that change frequently

Index Types

csharp
// Single column index
builder.HasIndex(e => e.Name);

// Composite index
builder.HasIndex(e => new { e.BudgetId, e.CreatedDate });

// Unique index
builder.HasIndex(e => e.Email)
    .IsUnique();

// Filtered index (PostgreSQL)
builder.HasIndex(e => e.Status)
    .HasFilter("status = 'Active'");

// Covering index (include columns)
builder.HasIndex(e => e.BudgetId)
    .IncludeProperties(e => new { e.Name, e.Amount });

Multi-Tenant Database Patterns

Approach 1: Shared Database, Shared Schema

csharp
// Add TenantId to all entities
public abstract class TenantEntity
{
    public Guid TenantId { get; set; }
}

// Global query filter
modelBuilder.Entity<Budget>()
    .HasQueryFilter(b => b.TenantId == _currentTenantId);

// Index on TenantId
builder.HasIndex(e => e.TenantId);

Approach 2: Shared Database, Separate Schemas

csharp
// Use different schemas per tenant
builder.ToTable("budgets", schema: _tenantSchema);

Approach 3: Separate Databases

csharp
// Connection string per tenant
var connectionString = _configuration[$"ConnectionStrings:Tenant_{tenantId}"];

Performance Optimization Patterns

Query Optimization

csharp
// ✅ GOOD - Single query with Include
var budgets = await context.Budgets
    .Include(b => b.Goals)
    .Include(b => b.Debts)
    .Where(b => b.UserId == userId)
    .ToListAsync();

// ❌ BAD - N+1 query problem
var budgets = await context.Budgets
    .Where(b => b.UserId == userId)
    .ToListAsync();

foreach (var budget in budgets)
{
    budget.Goals = await context.Goals
        .Where(g => g.BudgetId == budget.BudgetId)
        .ToListAsync(); // Separate query for each budget!
}

Batch Operations

csharp
// ✅ GOOD - Batch insert
context.Budgets.AddRange(budgets);
await context.SaveChangesAsync();

// ❌ BAD - Individual inserts
foreach (var budget in budgets)
{
    context.Budgets.Add(budget);
    await context.SaveChangesAsync(); // Multiple round trips!
}

Projection for Performance

csharp
// ✅ GOOD - Select only needed columns
var budgetNames = await context.Budgets
    .Where(b => b.UserId == userId)
    .Select(b => new { b.BudgetId, b.Name })
    .ToListAsync();

// ❌ BAD - Load entire entities
var budgets = await context.Budgets
    .Where(b => b.UserId == userId)
    .ToListAsync();
var budgetNames = budgets.Select(b => new { b.BudgetId, b.Name });

Common Migration Pitfalls

❌ Avoid These Mistakes

  1. Breaking Changes Without Data Migration

    • ❌ Renaming column without migrating data
    • ✅ Add new column, migrate data, drop old column
  2. Missing Indexes on Foreign Keys

    • ❌ Foreign key without index
    • ✅ Index all foreign key columns
  3. Not Reviewing Generated SQL

    • ❌ Blindly applying migrations
    • ✅ Review SQL script before applying
  4. Applying Migrations Manually in Production

    • ❌ Running dotnet ef database update in prod
    • ✅ Use CI/CD pipeline with SQL scripts
  5. No Rollback Plan

    • ❌ Only testing Up migration
    • ✅ Test both Up and Down migrations
  6. Forgetting Indexes After Data Migration

    • ❌ Large data operation without removing indexes first
    • ✅ Drop indexes, migrate data, recreate indexes

Migration Review Checklist

Before Creating Migration

  • Entity changes reviewed and approved
  • Understand impact on existing data
  • Plan for data migration if needed
  • Consider index requirements
  • Identify breaking changes

After Generating Migration

  • Review generated SQL (use migrations script)
  • Verify Up migration creates correct schema
  • Verify Down migration rolls back correctly
  • Check for missing indexes
  • Ensure data migrations preserve integrity

Before Applying Migration

  • Migration tested in development
  • Rollback tested in development
  • Database backup created
  • Deployment window scheduled (if needed)
  • Team notified of breaking changes

After Applying Migration

  • Verify schema changes applied correctly
  • Run smoke tests
  • Check database performance
  • Monitor error logs
  • Document migration in changelog

Checklist Before Completion

  • Migrations tested in development environment
  • Rollback migrations tested
  • SQL scripts reviewed for correctness
  • Indexes defined on foreign keys
  • Data migration logic validated
  • Breaking changes documented
  • Seed data included if needed
  • Performance impact assessed
  • Backup plan in place
  • Team informed of schema changes