diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/Data/DataSeederMiddleware.cs b/templates/app-nolayers/MyCompanyName.MyProjectName/Data/DataSeederMiddleware.cs deleted file mode 100644 index eede958c44..0000000000 --- a/templates/app-nolayers/MyCompanyName.MyProjectName/Data/DataSeederMiddleware.cs +++ /dev/null @@ -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 _logger; - private readonly IDataSeeder _dataSeeder; - - public DataSeederMiddleware( - ILogger 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) - ); - } -} diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/Data/MyProjectNameDbMigrationService.cs b/templates/app-nolayers/MyCompanyName.MyProjectName/Data/MyProjectNameDbMigrationService.cs new file mode 100644 index 0000000000..730786930d --- /dev/null +++ b/templates/app-nolayers/MyCompanyName.MyProjectName/Data/MyProjectNameDbMigrationService.cs @@ -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 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.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(); + 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) + ); + } +} diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/Data/MyProjectNameEFCoreDbSchemaMigrator.cs b/templates/app-nolayers/MyCompanyName.MyProjectName/Data/MyProjectNameEFCoreDbSchemaMigrator.cs new file mode 100644 index 0000000000..6726069d0d --- /dev/null +++ b/templates/app-nolayers/MyCompanyName.MyProjectName/Data/MyProjectNameEFCoreDbSchemaMigrator.cs @@ -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() + .Database + .MigrateAsync(); + } +} diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/Entities/.gitkeep b/templates/app-nolayers/MyCompanyName.MyProjectName/Entities/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/app-nolayers/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index a38030cf8f..29e8decb79 100644 --- a/templates/app-nolayers/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/app-nolayers/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -103,8 +103,4 @@ - - - - diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/MyProjectNameModule.cs b/templates/app-nolayers/MyCompanyName.MyProjectName/MyProjectNameModule.cs index fa89c29ff0..4102ec66a5 100644 --- a/templates/app-nolayers/MyCompanyName.MyProjectName/MyProjectNameModule.cs +++ b/templates/app-nolayers/MyCompanyName.MyProjectName/MyProjectNameModule.cs @@ -288,7 +288,6 @@ public class MyProjectNameModule : AbpModule app.UseErrorPage(); } - app.UseMiddleware(); app.UseCorrelationId(); app.UseStaticFiles(); app.UseRouting(); diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/Program.cs b/templates/app-nolayers/MyCompanyName.MyProjectName/Program.cs index 87d4d61515..75962c0169 100644 --- a/templates/app-nolayers/MyCompanyName.MyProjectName/Program.cs +++ b/templates/app-nolayers/MyCompanyName.MyProjectName/Program.cs @@ -1,26 +1,35 @@ +using MyCompanyName.MyProjectName.Data; using Serilog; using Serilog.Events; + namespace MyCompanyName.MyProjectName; public class Program { public async static Task Main(string[] args) { - Log.Logger = new LoggerConfiguration() + var loggerConfiguration = new LoggerConfiguration() #if DEBUG - .MinimumLevel.Debug() + .MinimumLevel.Debug() #else - .MinimumLevel.Information() + .MinimumLevel.Information() #endif - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) - .Enrich.FromLogContext() - .WriteTo.Async(c => c.File("Logs/logs.txt")) + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Async(c => c.File("Logs/logs.txt")) #if DEBUG - .WriteTo.Async(c => c.Console()) + .WriteTo.Async(c => c.Console()); #endif - .CreateLogger(); + if (IsMigrateDatabase(args)) + { + loggerConfiguration.MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning); + loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Warning); + loggerConfiguration.MinimumLevel.Override("IdentityServer4.Startup", LogEventLevel.Warning); + } + + Log.Logger = loggerConfiguration.CreateLogger(); try { @@ -32,6 +41,12 @@ public class Program var app = builder.Build(); await app.InitializeApplicationAsync(); + if (IsMigrateDatabase(args)) + { + await app.Services.GetRequiredService().MigrateAsync(); + return 0; + } + Log.Information("Starting MyCompanyName.MyProjectName."); await app.RunAsync(); return 0; @@ -51,4 +66,9 @@ public class Program Log.CloseAndFlush(); } } + + private static bool IsMigrateDatabase(string[] args) + { + return args.Any(x => x.Contains("--migrate-database", StringComparison.OrdinalIgnoreCase)); + } } diff --git a/templates/app-nolayers/MyCompanyName.MyProjectName/Services/Dtos/.gitkeep b/templates/app-nolayers/MyCompanyName.MyProjectName/Services/Dtos/.gitkeep new file mode 100644 index 0000000000..e69de29bb2