项目

单层解决方案:数据库迁移器

与分层解决方案模板不同,单层解决方案模板不包含独立的数据库迁移器项目。取而代之的是,主应用程序项目负责处理数据库迁移和种子数据操作。此模板中不包含 *.DbMigrator 项目。要管理数据库迁移和种子数据,您可以使用根目录下的 migrate-database.ps1 脚本,或在主应用程序项目目录中运行 dotnet run --migrate-database 命令。

单层解决方案:数据库迁移器

迁移完成后,控制台会显示一条消息。您可以通过检查数据库来验证迁移是否成功。

数据库迁移服务

在项目的 Data 文件夹下,BookstoreDbMigrationService 类负责数据库迁移和种子数据操作。当应用程序以 --migrate-database 参数启动时,Program 类会调用 MigrateAsync 方法来迁移数据库。

首先,它会检查数据库是否已创建,并应用待处理的迁移。然后,通过 SeedAsync 方法填充初始数据。

public async Task MigrateAsync()
{
    var initialMigrationAdded = AddInitialMigrationIfNotExist();

    if (initialMigrationAdded)
    {
        return;
    }

    Logger.LogInformation("开始数据库迁移...");

    await MigrateDatabaseSchemaAsync();
    await SeedDataAsync();

    Logger.LogInformation($"成功完成主机数据库迁移。");

    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($"成功完成 {tenant.Name} 租户数据库迁移。");
    }

    Logger.LogInformation("成功完成所有数据库迁移。");
    Logger.LogInformation("您可以安全地结束此进程...");
}

BookstoreDbSchemaMigrator 类在 MigrateDatabaseSchemaAsync 方法中用于数据库迁移过程,负责将迁移应用到数据库。

public class BookstoreDbSchemaMigrator : ITransientDependency
{
    private readonly IServiceProvider _serviceProvider;

    public BookstoreDbSchemaMigrator(
        IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task MigrateAsync()
    {
        
        /* 我们特意从 IServiceProvider 解析 BookstoreDbContext
         * (而不是直接注入)
         * 以正确获取当前作用域中当前租户的连接字符串。
         */

        await _serviceProvider
            .GetRequiredService<BookstoreDbContext>()
            .Database
            .MigrateAsync();

    }
}

在本文档中