如何在所有C#服务中复用多租户数据库切换代码?
多租户数据库名称替换的代码复用方案
方案1:抽象基类封装逻辑
把修改连接字符串的通用逻辑抽成抽象基类,所有需要该逻辑的服务都继承这个基类,彻底避免重复代码。
public abstract class BaseTenantService { protected readonly erp_colombiaContext _context; protected BaseTenantService(erp_colombiaContext context, IHttpContextAccessor httpContextAccessor) { _context = context; var user = httpContextAccessor.HttpContext?.User; if (user?.Claims.Any() == true) { var companyName = user.Claims.FirstOrDefault(x => x.Type == "CompanyName")?.Value; if (!string.IsNullOrEmpty(companyName)) { var connection = _context.Database.GetDbConnection(); connection.ConnectionString = connection.ConnectionString.Replace("erp_colombia", companyName); } } } }
业务服务只需继承基类,专注自身业务逻辑:
public class MyService : BaseTenantService, IMyService { public MyService(erp_colombiaContext context, IHttpContextAccessor httpContextAccessor) : base(context, httpContextAccessor) { // 这里写当前服务的特有逻辑 } }
方案2:扩展方法封装逻辑
把修改连接字符串的逻辑封装成DbContext的扩展方法,每个服务里直接调用即可,代码更轻量化。
public static class TenantDbContextExtensions { public static void SwitchToTenantDatabase(this erp_colombiaContext context, IHttpContextAccessor httpContextAccessor) { var user = httpContextAccessor.HttpContext?.User; if (user?.Claims.Any() == true) { var companyName = user.Claims.FirstOrDefault(x => x.Type == "CompanyName")?.Value; if (!string.IsNullOrEmpty(companyName)) { var connection = context.Database.GetDbConnection(); connection.ConnectionString = connection.ConnectionString.Replace("erp_colombia", companyName); } } } }
业务服务中调用扩展方法:
public class MyService : IMyService { private readonly erp_colombiaContext _context; public MyService(erp_colombiaContext context, IHttpContextAccessor httpContextAccessor) { _context = context; _context.SwitchToTenantDatabase(httpContextAccessor); } }
方案3:EF Core拦截器(全局生效,推荐)
利用EF Core的DbConnectionInterceptor,在数据库连接打开前自动替换数据库名称,无需在每个服务中编写任何代码,全局统一处理。
public class TenantConnectionInterceptor : DbConnectionInterceptor { private readonly IHttpContextAccessor _httpContextAccessor; public TenantConnectionInterceptor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) { ModifyConnectionString(connection); return base.ConnectionOpening(connection, eventData, result); } public override Task<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { ModifyConnectionString(connection); return base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken); } private void ModifyConnectionString(DbConnection connection) { var user = _httpContextAccessor.HttpContext?.User; if (user?.Claims.Any() == true) { var companyName = user.Claims.FirstOrDefault(x => x.Type == "CompanyName")?.Value; if (!string.IsNullOrEmpty(companyName) && connection.ConnectionString.Contains("erp_colombia")) { connection.ConnectionString = connection.ConnectionString.Replace("erp_colombia", companyName); } } } }
在Program.cs中注册拦截器:
builder.Services.AddDbContext<erp_colombiaContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); options.AddInterceptors(sp => sp.GetRequiredService<TenantConnectionInterceptor>()); }); builder.Services.AddScoped<TenantConnectionInterceptor>(); builder.Services.AddHttpContextAccessor();
这种方案一劳永逸,所有使用erp_colombiaContext的地方都会自动切换到租户数据库,无需在服务层做任何额外处理。
内容的提问来源于stack exchange,提问作者J.C
相关产品推荐
相关产品推荐

