C#多租户-EF Core动态链接
场景
Sass产品一般都要考虑租户中的数据隔离,在数据库层面一般分为几种做法:
- 独立数据库
- 独立的表
- 表中加入租户标识
本文讲解使用EF Core时,如何在一个服务根据租户身份使用不同的数据库(独立数据库)
实现
在AddDbContext
的时候,使用提前注入好实现的的ISQLStrResolver
服务的GetConnectionString
方法动态获取链接字符串(我这里还有一个TenantInfo
包含数据库类型的字段)
services.AddDbContext<TDbContect>((serviceProvider, options) =>
{
var tenantInfo = serviceProvider.GetRequiredService<TenantInfo>();
var sqlStrResolver = serviceProvider.GetRequiredService<ISQLStrResolver>();
if(tenantInfo.CddDataBaseType == CddDataBaseTypeEnumPCM.SQLServer)
{
options.UseSqlServer(sqlStrResolver.GetConnectionString());
}
else
{
options.UseNpgsql(sqlStrResolver.GetConnectionString());
}
});
ISQLStrResolver
实现(因为多租户数据库连接是从中间件中请求租户中心获取的,测试环境下不想这么麻烦,所以会使用本地的连接字符串)
public SQLStrResolver(TenantInfo tenantInfo, IConfiguration configuration,
IWebHostEnvironment env, ILogger<SQLConnectionStrResolver> logger)
{
_tenantInfo = tenantInfo;
_configuration = configuration;
_env = env;
}
public string GetConnectionString()
{
var connectString = "";
// 只有本地运行环境允许这样
if (_env.IsDevelopment())
{
connectString = _configuration.GetConnectionString("DbContext");
}
else if (!string.IsNullOrWhiteSpace(_tenantInfo.ConnectionString))
{
connectString = _tenantInfo.ConnectionString;
}
return connectString;
}
多租户中间件的实现(简略实现):
public class TenantMiddleware : IMiddleware
{
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _configuration;
private readonly ILogger<TenantMiddleware> _logger;
private readonly IMemoryCache _memoryCache;
public TenantMiddleware(IWebHostEnvironment env,
IConfiguration configuration, ILogger<TenantMiddleware> logger,
IMemoryCache memoryCache)
{
_env = env;
_configuration = configuration;
_logger = logger;
_memoryCache = memoryCache;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var tenantInfo = context.RequestServices.GetRequiredService<TenantInfo>();
var serverRunTimeParameter = context.RequestServices.GetRequiredService<ServerRunTimeParameterPCM>();
var tenantIdentifier = context.GetTenantIdentifier();
tenantInfo.TenantIdentifier = context.GetTenantIdentifier();
if (!string.IsNullOrWhiteSpace(tenantIdentifier))
{
// use MemoryCache
var cacheKey = tenantIdentifier + tenantInfo.ServerIdentifier + tenantInfo.ClientIdentifier;
var runTimeParameter = _memoryCache.Get<ServerRunTimeParameterPCM>(cacheKey);
if(runTimeParameter == null)
{
var res = await TenantServerPCRequest.GetServerRunTimeParameter(serverIdentifier, context.GetNeeds());
if (!res.IsSuccessCode)
{
_logger.LogError(res.Message);
context.Response.StatusCode = 500;
return;
}
runTimeParameter = res.PCMModel;
var copyedData = JsonConvert.DeserializeObject<ServerRunTimeParameterPCM>(JsonConvert.SerializeObject(runTimeParameter));
_memoryCache.Set(cacheKey, copyedData, TimeSpan.FromMinutes(30));
}
try
{
// 填充数据库类型和数据库连接字符串
tenantInfo.ConnectionString = DecryptHelper.Decrypt(runTimeParameter.SQLConnectionString);
tenantInfo.CddDataBaseType = runTimeParameter.CddDataBaseType;
if (_env.IsDevelopment())
{
var dbType = _configuration.GetValue<string>("DataBaseType");
if (string.IsNullOrWhiteSpace(dbType))
{
tenantInfo.CddDataBaseType = CddDataBaseTypeEnumPCM.SQLServer;
}
else if (dbType == "SQLServer")
{
tenantInfo.CddDataBaseType = CddDataBaseTypeEnumPCM.SQLServer;
}
else if (dbType == "PostgreSQL")
{
tenantInfo.CddDataBaseType = CddDataBaseTypeEnumPCM.PostgreSQL;
}
}
}
catch (Exception ex)
{
context.Response.StatusCode = 400;
return;
}
await next(context);
}
else
{
_logger.LogError("TenantIdentifier Or ClientIdentifier Was No Found");
context.Response.StatusCode = 400;
}
}
}
其实就是经过中间件,将所需的租户使用的数据库资源连接字符串请求过来,之后在每次使用DbContext的时候,会先经过
var tenantInfo = serviceProvider.GetRequiredService<TenantInfo>();
var sqlStrResolver = serviceProvider.GetRequiredService<ISQLStrResolver>();
if(tenantInfo.CddDataBaseType == CddDataBaseTypeEnumPCM.SQLServer)
{
options.UseSqlServer(sqlStrResolver.GetConnectionString());
}
else
{
options.UseNpgsql(sqlStrResolver.GetConnectionString());
}
数据库连接字符串就被动态填充进来了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构