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

EF Core多租户动态Schema迁移异常:运行时无法修改表Schema

多租户ASP.NET Core Web API中EF Core 8无法动态修改表Schema的问题

我正在开发一个多租户ASP.NET Core Web API项目,使用EF Core 8作为ORM框架,数据库采用SQL Server。项目需要支持运行时创建数据库,并将表的Schema从默认的dbo修改为巴西CPF文档编号。

目前迁移脚本已准备完成,运行Migrate()方法能正确创建数据库,但始终无法修改Schema——EF Core一直把表保留在dbo Schema下,完全不遵循指定的参数。这个问题我已经折腾了5个月,尝试各种方法都没解决,求可行的解决方案。


迁移执行代码

//Apply Migrations
public static void Test(this IServiceProvider serviceProvider, IConfiguration configuration, TenantRequestViewModel tenantRequestViewModel)
{
    using (var scope = serviceProvider.CreateScope())
    {
        var currentTenantRepo = scope.ServiceProvider.GetRequiredService<ICurrentTenantRepository>();

        if (!currentTenantRepo.SetTenantAsync(tenantRequestViewModel.DocumentNumber).Result) { { } }

        string dbName = DataBaseConst.DATABASE_NAME_ERP_VET + "-" + tenantRequestViewModel.DocumentNumber;
        string defaultConnectionString = configuration.GetConnectionString("DefaultConnection");
        string connectionString = currentTenantRepo.ConnectionString ?? defaultConnectionString.Replace(DataBaseConst.DATABASE_NAME_ERP_VET, dbName);

        var appDbContextOptionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
        appDbContextOptionsBuilder.UseSqlServer(connectionString);

        var mediatorHandler = scope.ServiceProvider.GetRequiredService<IMediatorHandler>();

        using (var appDbContext = new AppDbContext(appDbContextOptionsBuilder.Options, currentTenantRepo, mediatorHandler, currentTenantRepo.Schema))
        {
            Console.WriteLine($"Current Schema: {currentTenantRepo.Schema}");

            if (appDbContext.Database.GetPendingMigrations().Any())
            {
                appDbContext.Database.Migrate();
            }
        }       
    }
}

AppDbContext代码

// My Context
public sealed class AppDbContext : DbContext, IUnitOfWork
{
    private readonly IMediatorHandler _mediatorHandler;

    private readonly ICurrentTenantRepository _currentTenantRepository;
    public Guid CurrentTenantId { get; set; }
    public string? CurrentTenantConnectionString { get; set; }
    public string? CurrentSchema { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options, ICurrentTenantRepository currentTenantRepository, IMediatorHandler mediatorHandler, string? schema = null) : base(options)
    {
        _mediatorHandler = mediatorHandler;
        _currentTenantRepository = currentTenantRepository;

        if (_currentTenantRepository is not null)
        {
            CurrentTenantId = _currentTenantRepository.TenantId;
            CurrentTenantConnectionString = _currentTenantRepository.ConnectionString;
            CurrentSchema = string.IsNullOrEmpty(schema) ? _currentTenantRepository.Schema : schema ;
        }

        //ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        //ChangeTracker.AutoDetectChangesEnabled = false;
    }

    public DbSet<Customer> Customers { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Ignore<ValidationResult>();
        modelBuilder.Ignore<Event>();
        modelBuilder.Ignore<CreateTenantRequest>();
        modelBuilder.Ignore<TenantResponse>();
        modelBuilder.Ignore<Tenant>();

        modelBuilder.HasDefaultSchema(CurrentSchema);
        
        if (!string.IsNullOrEmpty(CurrentSchema))
        {
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                entityType.SetSchema(CurrentSchema);
            }              
        }           

        foreach (var property in modelBuilder.Model.GetEntityTypes().SelectMany(
            e => e.GetProperties().Where(p => p.ClrType == typeof(string))))
            property.SetColumnType("varchar(100)");
            
        //Schema name passed to table configuration
        modelBuilder.ApplyConfiguration(new CustomerMap(CurrentSchema));

        base.OnModelCreating(modelBuilder);
    }

    // On Configuring -- dynamic connection string, fires on every request
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        string? tenantConnectionString = CurrentTenantConnectionString;

        if (!string.IsNullOrEmpty(tenantConnectionString)) // use tenant db if one is specified
        {
            _ = optionsBuilder.UseSqlServer(tenantConnectionString);
        }

        optionsBuilder.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory>();

        base.OnConfiguring(optionsBuilder);
    }
}

自定义Model缓存键工厂代码

// CustomModelCacheKeyFactory
public class CustomModelCacheKeyFactory : IModelCacheKeyFactory
{
    public object Create(DbContext context, bool designTime)
    {
        var contextWithSchema = context as AppDbContext;
        // 使用租户ID或特定Schema作为缓存键,结合设计时标识
        return (contextWithSchema?.CurrentSchema, context.GetType(), designTime);
    }
}

请求示例

{
  "documentNumber": "03350593097",
  "workspaceName": "My_Work_1", 
  "phoneNumber": "27996356704", 
  "email": "jalberromano@msn.com",
  "password": "123456",
  "confirmPassword": "123456",  
  "subscriptionPlanId": "0335a5fe-4754-4ed0-1434-08dd1937d5aa",//启动API后从表中获取ID
  "cultureCode": "pt-BR",
  "isolated": true
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 14:04:59