Skip to main content

Running Migrations

Migrations define schema changes, but they must be applied/executed to actually modify the database. In PearDrop, migrations are applied using the PearDrop CLI which automatically discovers and runs migrations for all DbContexts.

Overview: When Migrations Are Applied

EnvironmentHowWhen
Local DevelopmentCLI: peardrop migrateManually, after generating migrations
Staging/ProductionCI/CD Pipeline: peardrop migrateDuring deployment (automated)
Program StartupProgrammatic: await serviceProvider.ApplyMyModuleMigrationsAsync()Application initialization (optional)

Applying Migrations with PearDrop CLI

Basic Migration Runner

The PearDrop CLI provides a unified command that discovers all DbContexts (write and read) and applies pending migrations:

# Run all pending migrations
dotnet tool run peardrop migrate

# Or if CLI is installed globally
peardrop migrate

What it does:

  1. Discovers all DbContext classes in your project
  2. Reads connection strings from appsettings.json
  3. Applies pending migrations to the database
  4. Works for both write and read contexts automatically

Expected output:

Applying migrations...
Apply migration CreateIdentitySchema...
Apply migration AddEquipmentAggregate...
Apply migration AddEquipmentProjection...
Done.

With Database Seeding

To apply migrations and run seed data:

peardrop migrate --seed

This executes seeding logic defined in your DbContext.OnModelCreating() or custom seed classes.

Connection String Resolution

The peardrop migrate command automatically finds connection strings in this order:

  1. Module-specific strings (for framework modules):

    • ConnectionStrings:PearDrop-Auth
    • ConnectionStrings:PearDrop-Files
    • ConnectionStrings:PearDrop-Multitenancy
  2. Default application string:

    • ConnectionStrings:PearDrop
  3. Deprecated fallback:

    • PearDrop:modules:core:PrimaryConnectionString

Example appsettings.json:

{
"ConnectionStrings": {
"PearDrop": "Server=localhost,1440;Database=MyApp;User Id=sa;Password=...;TrustServerCertificate=True;",
"PearDrop-Auth": "Server=localhost,1440;Database=MyApp;User Id=sa;Password=...;TrustServerCertificate=True;"
}
}
tip

Point all connection strings to the same database unless you need physical separation for specific modules.

Programmatic Migration Application

For scenarios where you need migrations to run automatically on app startup, use the module's migration helper method:

// Program.cs
var app = builder.Build();

// Apply migrations programmatically before running the app
await app.Services.ApplyMyModuleMigrationsAsync();

app.Run();

When to use programmatic migrations:

  • Development environment for convenience
  • Docker container startup (ensures DB is ready)
  • Testing environments

When NOT to use:

  • Production deployments (use CI/CD pipeline with peardrop migrate)
  • Scenarios requiring migration rollback control
  • When migrations need manual approval

Example: Auth Module

Framework modules provide extension methods:

await app.Services.ApplyPearDropAuthenticationMigrationsAsync();

Example: Custom Module

Create your own extension method:

public static class ServiceProviderExtensions
{
public static async Task<IServiceProvider> ApplyMyModuleMigrationsAsync(
this IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();

// Apply write model migrations
var writeDbContextFactory = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<MyModuleWriteDbContext>>();
var writeCtx = await writeDbContextFactory.CreateDbContextAsync();
await writeCtx.Database.MigrateAsync();

// Apply read model migrations
var readDbContextFactory = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<MyModuleReadDbContext>>();
var readCtx = await readDbContextFactory.CreateDbContextAsync();
await readCtx.Database.MigrateAsync();

return serviceProvider;
}
}

CI/CD Pipeline Integration

Azure DevOps Pipeline

- task: DotNetCoreCLI@2
displayName: 'Apply Database Migrations'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'run peardrop migrate'
workingDirectory: '$(Build.SourcesDirectory)'

Azure Pipelines

- task: DotNetCoreCLI@2
displayName: 'Apply Database Migrations'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'run peardrop migrate'
workingDirectory: '$(Build.SourcesDirectory)'

Docker Container Startup

# In your Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY . .
RUN dotnet tool restore
RUN dotnet build

# In your entrypoint script
ENTRYPOINT ["sh", "-c", "dotnet tool run peardrop migrate && dotnet run"]

Troubleshooting

"peardrop: command not found"

Restore the CLI tool:

dotnet tool restore

Or use the full path:

dotnet tool run peardrop migrate

Connection String Not Found

Verify appsettings.json contains:

{
"ConnectionStrings": {
"PearDrop": "Server=..."
}
}

Check the logs for connection string resolution order.### Database Connection Failed

Ensure SQL Server is running:

# Check Docker container status
docker ps

# Start SQL Server if not running
docker-compose up -d

Verify connection string has correct credentials and server address.

Migration Already Applied Error

This usually means the migration history table is out of sync. Check applied migrations in the database:

-- Query EF Core migrations history
SELECT * FROM [__EFMigrationsHistory] ORDER BY MigrationId DESC;

If needed, manually add/remove entries, or use a fresh database for development.

Best Practices

✅ DO

  • Use PearDrop CLI for local development: peardrop migrate
  • Automate migrations in CI/CD pipelines before deployment
  • Test migrations in a staging environment before production
  • Version control migrations - commit all migration files
  • Separate write/read migrations - PearDrop CLI handles this automatically

❌ DON'T

  • Don't use dotnet ef database update directly - use peardrop migrate instead
  • Don't manually edit migration files after applying them
  • Don't skip migrations - always apply in sequential order
  • Don't assume automatic application - be explicit (CLI or programmatic helper)
  • Don't run migrations from multiple processes simultaneously (race conditions)

Next Steps