sozsoft-platform/api/src/Sozsoft.Platform.Domain/Data/PlatformDbMigrationService.cs

300 lines
9.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Sozsoft.Platform.Data.Seeds;
using StackExchange.Redis;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TenantManagement;
namespace Sozsoft.Platform.Data;
public class PlatformDbMigrationService : ITransientDependency
{
public ILogger<PlatformDbMigrationService> Logger { get; set; }
private readonly IDataSeeder _dataSeeder;
private readonly IEnumerable<IPlatformDbSchemaMigrator> _dbSchemaMigrators;
private readonly ITenantRepository _tenantRepository;
private readonly ICurrentTenant _currentTenant;
private readonly IConfiguration _configuration;
private readonly IEnumerable<IFinalSeeder> _finalSeeders;
private readonly IListFormSeedDataApplier _listFormSeedDataApplier;
public PlatformDbMigrationService(
IDataSeeder dataSeeder,
IEnumerable<IPlatformDbSchemaMigrator> dbSchemaMigrators,
ITenantRepository tenantRepository,
ICurrentTenant currentTenant,
IConfiguration configuration,
IEnumerable<IFinalSeeder> finalSeeders,
IListFormSeedDataApplier listFormSeedDataApplier)
{
_dataSeeder = dataSeeder;
_dbSchemaMigrators = dbSchemaMigrators;
_tenantRepository = tenantRepository;
_currentTenant = currentTenant;
_configuration = configuration;
_finalSeeders = finalSeeders;
_listFormSeedDataApplier = listFormSeedDataApplier;
Logger = NullLogger<PlatformDbMigrationService>.Instance;
}
public async Task MigrateAsync()
{
var seed = _configuration.GetValue("Seed", false);
Logger.LogInformation($"Migrating database. Seed: {seed}. Env: {Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}");
//Clear Cache
var redis = ConnectionMultiplexer.Connect(_configuration["Redis:Configuration"]!);
var db = redis.GetDatabase();
db.Execute("FLUSHALL");
var initialMigrationAdded = AddInitialMigrationIfNotExist();
if (initialMigrationAdded)
{
return;
}
Logger.LogInformation("Started database migrations...");
await MigrateDatabaseSchemaAsync();
if (seed)
{
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);
}
}
if (seed)
{
await SeedDataAsync(tenant);
}
}
Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations.");
}
Logger.LogInformation("Successfully completed all database migrations.");
await ExecuteFinalSeedersAsync(tenants);
Logger.LogInformation("You can safely end this process...");
}
/// <summary>
/// Tüm host ve tenant migration/seed adımları bittikten sonra,
/// {host|tenants/{tenantId}}/{sql|postgres}/execute klasöründeki script'lerin dosya adından
/// türeyen procedure'lerini ilgili bağlamda çalıştırır. İlgili tenant klasörü yoksa
/// o tenant adımı sessizce atlanır.
/// </summary>
private async Task ExecuteFinalSeedersAsync(List<Tenant> tenants)
{
if (!_finalSeeders.Any())
{
Logger.LogInformation("No SQL executor registered, skipping execute scripts.");
return;
}
Logger.LogInformation(
"Executing SQL scripts for host and {TenantCount} tenant(s)...",
tenants.Count);
using (_currentTenant.Change(null))
{
foreach (var executor in _finalSeeders)
{
Logger.LogInformation("Running SQL scripts for host...");
await executor.ExecuteAsync();
Logger.LogInformation("Completed SQL scripts for host.");
}
}
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
foreach (var executor in _finalSeeders)
{
Logger.LogInformation(
"Running SQL scripts for {TenantName} tenant...",
tenant.Name);
await executor.ExecuteAsync(tenant.Id);
Logger.LogInformation(
"Completed SQL scripts for {TenantName} tenant.",
tenant.Name);
}
}
}
Logger.LogInformation("Successfully completed all SQL scripts.");
}
private async Task MigrateDatabaseSchemaAsync(Tenant? tenant = null)
{
Logger.LogInformation(
$"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database...");
foreach (var migrator in _dbSchemaMigrators)
{
await migrator.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, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue)
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, PlatformConsts.AbpIdentity.User.AdminPasswordDefaultValue)
.WithProperty(IdentityDataSeedContributor.AdminUserNamePropertyName, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue)
);
// Veri seed dosyalari ListForm tanimlarina ve tablolara dayandigi icin butun
// contributor'lar bittikten sonra uygulanir; ABP contributor sirasi garanti degildir.
await _listFormSeedDataApplier.ApplyAsync(tenant?.Id);
}
private bool AddInitialMigrationIfNotExist()
{
try
{
if (!DbMigrationsProjectExists())
{
return false;
}
}
catch (Exception)
{
return false;
}
try
{
if (!MigrationsFolderExists())
{
AddInitialMigration();
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message);
return false;
}
}
private bool DbMigrationsProjectExists()
{
var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath();
return dbMigrationsProjectFolder != null;
}
private bool MigrationsFolderExists()
{
var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath();
return dbMigrationsProjectFolder != null && Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations"));
}
private void AddInitialMigration()
{
Logger.LogInformation("Creating initial migration...");
string argumentPrefix;
string fileName;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
argumentPrefix = "-c";
fileName = "/bin/bash";
}
else
{
argumentPrefix = "/C";
fileName = "cmd.exe";
}
var procStartInfo = new ProcessStartInfo(fileName,
$"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\"\""
);
try
{
Process.Start(procStartInfo);
}
catch (Exception)
{
throw new Exception("Couldn't run ABP CLI...");
}
}
private string? GetEntityFrameworkCoreProjectFolderPath()
{
var slnDirectoryPath = GetSolutionDirectoryPath();
if (slnDirectoryPath == null)
{
throw new Exception("Solution folder not found!");
}
var srcDirectoryPath = Path.Combine(slnDirectoryPath, "src");
return Directory.GetDirectories(srcDirectoryPath)
.FirstOrDefault(d => d.EndsWith(".EntityFrameworkCore"));
}
private string? GetSolutionDirectoryPath()
{
var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
while (currentDirectory != null && Directory.GetParent(currentDirectory.FullName) != null)
{
currentDirectory = Directory.GetParent(currentDirectory.FullName);
if (currentDirectory != null && Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null)
{
return currentDirectory.FullName;
}
}
return null;
}
}