You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为多租户场景动态应用EF Core迁移至多个数据库

多租户.NET Core API运行时自动应用数据库迁移方案

核心结论:不要在ApiDbContext构造函数中执行迁移

构造函数无法处理异步操作,且每次请求都会创建DbContext实例,会导致迁移重复执行,甚至引发服务初始化、作用域依赖的问题。下面是两种可行的编程实现方案:


1. 封装租户迁移服务

先创建专门的迁移执行服务,统一处理迁移逻辑:

public interface ITenantMigrationService
{
    Task ApplyMigrationsAsync(string connectionString);
}

public class TenantMigrationService : ITenantMigrationService
{
    public async Task ApplyMigrationsAsync(string connectionString)
    {
        // 针对当前租户连接字符串构建DbContext选项
        var dbOptions = new DbContextOptionsBuilder<ApiDbContext>()
            .UseSqlServer(connectionString)
            .Options;

        using var tenantContext = new ApiDbContext(dbOptions);
        // 自动应用所有未执行的迁移
        await tenantContext.Database.MigrateAsync();
    }
}

在服务注册中添加这个作用域服务:

// Program.cs/Startup.cs
services.AddScoped<ITenantMigrationService, TenantMigrationService>();

2. 选择合适的执行时机

方案一:创建租户时立即执行(推荐)

如果有创建租户的API接口,在生成租户连接字符串、创建数据库后直接调用迁移服务,避免后续请求重复检查:

[ApiController]
[Route("api/tenants")]
public class TenantController : ControllerBase
{
    private readonly ITenantMetaDataService _tenantMetaService;
    private readonly ITenantMigrationService _migrationService;

    public TenantController(ITenantMetaDataService tenantMetaService, ITenantMigrationService migrationService)
    {
        _tenantMetaService = tenantMetaService;
        _migrationService = migrationService;
    }

    [HttpPost]
    public async Task<IActionResult> CreateTenant([FromBody] TenantCreateDto dto)
    {
        // 1. 生成该租户的专属连接字符串
        var tenantConnStr = _tenantMetaService.GenerateConnectionString(dto.TenantId);
        
        // 2. 可选:先创建空数据库(如果数据库不存在)
        await CreateTenantDatabaseIfNotExists(tenantConnStr);
        
        // 3. 立即应用迁移
        await _migrationService.ApplyMigrationsAsync(tenantConnStr);

        return Ok(new { TenantId = dto.TenantId });
    }

    // 辅助方法:创建数据库(基于master库执行创建命令)
    private async Task CreateTenantDatabaseIfNotExists(string connectionString)
    {
        var connBuilder = new SqlConnectionStringBuilder(connectionString);
        var dbName = connBuilder.InitialCatalog;
        connBuilder.InitialCatalog = "master";

        using var masterConn = new SqlConnection(connBuilder.ToString());
        await masterConn.OpenAsync();
        var createCmd = masterConn.CreateCommand();
        createCmd.CommandText = $"IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'{dbName}') CREATE DATABASE [{dbName}]";
        await createCmd.ExecuteNonQueryAsync();
    }
}

方案二:首次请求租户时自动执行

如果租户数据库可能由外部系统创建,无法在租户创建时触发迁移,可以通过中间件在每个请求前检查并执行:

public class TenantMigrationMiddleware
{
    private readonly RequestDelegate _next;
    // 可选:用内存缓存记录已迁移的租户ID,避免重复检查
    private readonly IMemoryCache _cache;
    private const string MigratedTenantsKey = "MigratedTenants";

    public TenantMigrationMiddleware(RequestDelegate next, IMemoryCache cache)
    {
        _next = next;
        _cache = cache;
    }

    public async Task InvokeAsync(HttpContext context, ITenantMetaDataService tenantMetaService, ITenantMigrationService migrationService)
    {
        var tenantId = tenantMetaService.GetTenantId();
        var connStr = tenantMetaService.GetConnectionString();

        // 检查缓存:如果该租户已迁移,直接跳过
        var migratedTenants = _cache.GetHashSet<string>(MigratedTenantsKey) ?? new HashSet<string>();
        if (!migratedTenants.Contains(tenantId))
        {
            var dbOptions = new DbContextOptionsBuilder<ApiDbContext>()
                .UseSqlServer(connStr)
                .Options;

            using var tenantContext = new ApiDbContext(dbOptions);
            // 仅当有未执行的迁移时才执行
            if (tenantContext.Database.GetPendingMigrations().Any())
            {
                await migrationService.ApplyMigrationsAsync(connStr);
                migratedTenants.Add(tenantId);
                _cache.Set(MigratedTenantsKey, migratedTenants);
            }
        }

        await _next(context);
    }
}

// 注册中间件(放在租户识别中间件之后)
// Program.cs
app.UseMiddleware<TenantMigrationMiddleware>();

内容的提问来源于stack exchange,提问作者RedRose

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 11:55:34