mirror of https://github.com/abpframework/abp.git
Browse Source
Resolve #11384 ```cs dotnet run --migrate-database dotnet .\MyCompanyName.MyProjectName.dll --migrate-database ```pull/11386/head
8 changed files with 146 additions and 62 deletions
@ -1,48 +0,0 @@ |
|||||
using Volo.Abp.Data; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
using Volo.Abp.Identity; |
|
||||
|
|
||||
namespace MyCompanyName.MyProjectName.Data; |
|
||||
|
|
||||
public class DataSeederMiddleware : IMiddleware, ISingletonDependency |
|
||||
{ |
|
||||
private bool _hostSeeded; |
|
||||
|
|
||||
private readonly ILogger<DataSeederMiddleware> _logger; |
|
||||
private readonly IDataSeeder _dataSeeder; |
|
||||
|
|
||||
public DataSeederMiddleware( |
|
||||
ILogger<DataSeederMiddleware> logger, |
|
||||
IDataSeeder dataSeeder) |
|
||||
{ |
|
||||
_logger = logger; |
|
||||
_dataSeeder = dataSeeder; |
|
||||
} |
|
||||
|
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|
||||
{ |
|
||||
/* This logic is not safe if you are running multiple instances of your |
|
||||
* application in parallel. In that case, a distributed lock usage is suggested, |
|
||||
* or you can create another application for database migration/seed. |
|
||||
*/ |
|
||||
if (!_hostSeeded) |
|
||||
{ |
|
||||
await SeedHostDataAsync(); |
|
||||
_hostSeeded = true; |
|
||||
} |
|
||||
|
|
||||
await next(context); |
|
||||
} |
|
||||
|
|
||||
private Task SeedHostDataAsync() |
|
||||
{ |
|
||||
_logger.LogInformation($"Executing database seed..."); |
|
||||
|
|
||||
return _dataSeeder.SeedAsync(new DataSeedContext() |
|
||||
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, |
|
||||
IdentityDataSeedContributor.AdminEmailDefaultValue) |
|
||||
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, |
|
||||
IdentityDataSeedContributor.AdminPasswordDefaultValue) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,88 @@ |
|||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.TenantManagement; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Data; |
||||
|
|
||||
|
public class MyProjectNameDbMigrationService : ITransientDependency |
||||
|
{ |
||||
|
public ILogger<MyProjectNameDbMigrationService> Logger { get; set; } |
||||
|
|
||||
|
private readonly IDataSeeder _dataSeeder; |
||||
|
private readonly MyProjectNameEFCoreDbSchemaMigrator _dbSchemaMigrator; |
||||
|
private readonly ITenantRepository _tenantRepository; |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
public MyProjectNameDbMigrationService( |
||||
|
IDataSeeder dataSeeder, |
||||
|
MyProjectNameEFCoreDbSchemaMigrator dbSchemaMigrator, |
||||
|
ITenantRepository tenantRepository, |
||||
|
ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
_dataSeeder = dataSeeder; |
||||
|
_dbSchemaMigrator = dbSchemaMigrator; |
||||
|
_tenantRepository = tenantRepository; |
||||
|
_currentTenant = currentTenant; |
||||
|
|
||||
|
Logger = NullLogger<MyProjectNameDbMigrationService>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task MigrateAsync() |
||||
|
{ |
||||
|
Logger.LogInformation("Started database migrations..."); |
||||
|
|
||||
|
await MigrateDatabaseSchemaAsync(); |
||||
|
await SeedDataAsync(); |
||||
|
|
||||
|
Logger.LogInformation($"Successfully completed host database migrations."); |
||||
|
|
||||
|
var tenants = await _tenantRepository.GetListAsync(includeDetails: true); |
||||
|
|
||||
|
var migratedDatabaseSchemas = new HashSet<string>(); |
||||
|
foreach (var tenant in tenants) |
||||
|
{ |
||||
|
using (_currentTenant.Change(tenant.Id)) |
||||
|
{ |
||||
|
if (tenant.ConnectionStrings.Any()) |
||||
|
{ |
||||
|
var tenantConnectionStrings = tenant.ConnectionStrings |
||||
|
.Select(x => x.Value) |
||||
|
.ToList(); |
||||
|
|
||||
|
if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings)) |
||||
|
{ |
||||
|
await MigrateDatabaseSchemaAsync(tenant); |
||||
|
|
||||
|
migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await SeedDataAsync(tenant); |
||||
|
} |
||||
|
|
||||
|
Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations."); |
||||
|
} |
||||
|
|
||||
|
Logger.LogInformation("Successfully completed all database migrations."); |
||||
|
Logger.LogInformation("You can safely end this process..."); |
||||
|
} |
||||
|
|
||||
|
private async Task MigrateDatabaseSchemaAsync(Tenant tenant = null) |
||||
|
{ |
||||
|
Logger.LogInformation($"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database..."); |
||||
|
await _dbSchemaMigrator.MigrateAsync(); |
||||
|
} |
||||
|
|
||||
|
private async Task SeedDataAsync(Tenant tenant = null) |
||||
|
{ |
||||
|
Logger.LogInformation($"Executing {(tenant == null ? "host" : tenant.Name + " tenant")} database seed..."); |
||||
|
|
||||
|
await _dataSeeder.SeedAsync(new DataSeedContext(tenant?.Id) |
||||
|
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, IdentityDataSeedContributor.AdminEmailDefaultValue) |
||||
|
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, IdentityDataSeedContributor.AdminPasswordDefaultValue) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.Data; |
||||
|
|
||||
|
public class MyProjectNameEFCoreDbSchemaMigrator : ITransientDependency |
||||
|
{ |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
public MyProjectNameEFCoreDbSchemaMigrator( |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task MigrateAsync() |
||||
|
{ |
||||
|
/* We intentionally resolving the MyProjectNameDbContext |
||||
|
* from IServiceProvider (instead of directly injecting it) |
||||
|
* to properly get the connection string of the current tenant in the |
||||
|
* current scope. |
||||
|
*/ |
||||
|
|
||||
|
await _serviceProvider |
||||
|
.GetRequiredService<MyProjectNameDbContext>() |
||||
|
.Database |
||||
|
.MigrateAsync(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue