.NET Core 5 Web API 基于主DbContext动态修改从DbContext连接字符串方案咨询
实现方案说明
两种方案都可以实现需求,推荐优先用全局操作过滤器+作用域租户服务的方案,侵入性更低,不需要修改业务控制器代码。
方案1:全局操作过滤器(推荐)
步骤1:定义作用域级租户信息存储服务
用来在当前请求生命周期内传递查询到的租户数据库名
// 租户信息载体类 public class CurrentTenant { /// <summary> /// 租户专属数据库名 /// </summary> public string DatabaseName { get; set; } } // Program.cs中注册为作用域服务 builder.Services.AddScoped<CurrentTenant>();
步骤2:实现全局异步操作过滤器
在过滤器中完成租户校验、数据库名查询逻辑
public class TenantValidateFilter : IAsyncActionFilter { private readonly DbContextMaster _masterDb; private readonly CurrentTenant _currentTenant; private readonly IConfiguration _config; // 构造注入依赖 public TenantValidateFilter(DbContextMaster masterDb, CurrentTenant currentTenant, IConfiguration config) { _masterDb = masterDb; _currentTenant = currentTenant; _config = config; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 1. 从请求头获取租户标识(你已实现该逻辑,直接替换成你的获取方法即可) var tenantId = context.HttpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault(); if(string.IsNullOrEmpty(tenantId)) { context.Result = new BadRequestObjectResult("缺少租户标识"); return; } // 2. 主库查询租户对应的数据库名 var tenantDbName = await _masterDb.Tenants .Where(t => t.TenantId == tenantId) .Select(t => t.DbName) .FirstOrDefaultAsync(); if(string.IsNullOrEmpty(tenantDbName)) { context.Result = new BadRequestObjectResult("无效的租户标识"); return; } // 3. 存储数据库名到当前作用域租户服务 _currentTenant.DatabaseName = tenantDbName; // 执行后续控制器逻辑 await next(); } }
步骤3:注册全局过滤器与动态DbContextChild
在Program.cs中完成配置:
// 注册全局过滤器 builder.Services.AddControllers(options => { options.Filters.Add<TenantValidateFilter>(); }); // 注册动态连接串的DbContextChild builder.Services.AddDbContext<DbContextChild>((serviceProvider, opt) => { // 从当前作用域获取租户信息 var currentTenant = serviceProvider.GetRequiredService<CurrentTenant>(); // 读取连接串模板 var connTemplate = _config.GetConnectionString("DbContextChild"); // 替换占位符生成最终连接串 var finalConnStr = connTemplate.Replace("{dbName}", currentTenant.DatabaseName); // 配置数据库连接,这里以SqlServer为例,其他数据库修改对应扩展方法即可 opt.UseSqlServer(finalConnStr); });
方案2:基类控制器实现
如果你的业务场景中只有部分控制器需要租户校验,可以选择该方案:
- 定义基类ControllerBase,重写
OnActionExecutingAsync方法,将上述过滤器中的校验逻辑写入该方法 - 所有需要租户校验的业务控制器继承该基类即可
注意事项
- DbContextChild默认注册为Scoped生命周期,不要修改为Singleton,否则会出现租户串库问题
- 如果存在不需要租户校验的接口(如登录、健康检查),可以自定义
[SkipTenantValidate]特性,在过滤器中判断接口是否标记该特性,直接跳过校验逻辑即可
内容的提问来源于stack exchange,提问作者Marcoscdoni
相关产品推荐
相关产品推荐

