Runtime Database Migration
1. Background
In a modular or plugin-based architecture, modules may have their own data storage requirements (new tables, field extensions). Runtime database migration allows the system to automatically detect and apply database schema changes defined by modules during application startup.
2. Implementation Principle
Based on EF Core's Migration capability and ABP's database migration module.
2.1 DbContext Isolation
Typically, plugins or independent modules maintain their own DbContext to avoid polluting the host's main DbContext.
2.2 Migration Discovery
The system needs to identify all DbContexts registered in the container that need migration.
3. Migration Process
- Startup Check: During application startup (or through a dedicated migration tool/Job), the system iterates through registered Modules.
- Get Migrator: Retrieve the
IMigratorservice associated with the module (if defined). - Execute Migration: Call
database.MigrateAsync()to apply pending migrations to the database.
4. Configuration Example
4.1 Module DbContext Definition
[ConnectionStringName("MyPluginConnStr")]
public class MyPluginDbContext : AbpDbContext<MyPluginDbContext>
{
public DbSet<MyEntity> MyEntities { get; set; }
// Configuration...
}
4.2 Module Startup Migration Execution
In the OnPostApplicationInitialization phase or a dedicated Data Seeder:
public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
{
using (var scope = context.ServiceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyPluginDbContext>();
await dbContext.Database.MigrateAsync();
}
}
Note: In production environments, it is recommended to separate migration execution from application startup (using K8s Init Containers or separate pipelines) to avoid concurrency conflicts in multi-instance deployments.
5. Data Seeding (Data Seeding)
In addition to schema migration, modules often need to initialize basic data (e.g., default permissions, dictionary data).
Implement IDataSeedContributor interface:
public class MyPluginDataSeeder : IDataSeedContributor, ITransientDependency
{
public async Task SeedAsync(DataSeedContext context)
{
// Insert initial data logic
}
}
The system automatically calls these Seeders after migration completes.