Net 7 多租户API异步获取连接字符串配置DbContext的方法
异步获取租户连接字符串配置DbContext的实现方案
核心问题分析
原同步配置逻辑无法兼容异步获取连接字符串的场景,因为AddDbContext的配置委托是同步执行的。需要调整实现思路,将异步逻辑移到合适的时机完成。
方案一:基于DbContext工厂的异步初始化(推荐)
1. 重构租户服务,添加异步获取方法
扩展ITenantService,实现调用外部API异步获取连接字符串的逻辑,同时加入缓存避免重复调用:
public interface ITenantService { Task<string> GetConnectionStringAsync(); } public class TenantService : ITenantService { private readonly HttpClient _httpClient; private readonly IHttpContextAccessor _httpContextAccessor; private string _cachedConnStr; public TenantService(HttpClient httpClient, IHttpContextAccessor httpContextAccessor) { _httpClient = httpClient; _httpContextAccessor = httpContextAccessor; } public async Task<string> GetConnectionStringAsync() { if (!string.IsNullOrEmpty(_cachedConnStr)) return _cachedConnStr; // 从请求Token解析租户标识 var tenantId = _httpContextAccessor.HttpContext?.User.Claims .FirstOrDefault(c => c.Type == "TenantId")?.Value; if (string.IsNullOrEmpty(tenantId)) throw new InvalidOperationException("无法获取当前租户标识"); // 调用外部API获取连接字符串 var response = await _httpClient.GetAsync($"https://your-api-domain/tenants/{tenantId}/conn-str"); response.EnsureSuccessStatusCode(); _cachedConnStr = await response.Content.ReadAsStringAsync(); return _cachedConnStr; } }
2. 注册DbContext工厂并调整DbContext实现
使用AddDbContextFactory替代AddDbContext,将连接配置逻辑移到DbContext的异步初始化方法中:
// Program.cs 服务注册 builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpClient<ITenantService, TenantService>(); builder.Services.AddDbContextFactory<CustomDbContext>((services, options) => { // 先配置SQL Server的基础选项,留空连接字符串 options.UseSqlServer(x => x.UseHierarchyId().UseNetTopologySuite()) .ConfigureWarnings(x => x.Ignore(CoreEventId.PossibleIncorrectRequiredNavigationWithQueryFilterInteractionWarning)); }); // 自定义DbContext实现 public class CustomDbContext : DbContext, IDbContext { private readonly ITenantService _tenantService; public CustomDbContext(DbContextOptions<CustomDbContext> options, ITenantService tenantService) : base(options) { _tenantService = tenantService; } // 异步初始化连接方法,使用前需调用 public async Task InitializeConnectionAsync() { var connStr = await _tenantService.GetConnectionStringAsync(); var dbConn = Database.GetDbConnection(); if (dbConn.ConnectionString != connStr) { dbConn.ConnectionString = connStr; await dbConn.OpenAsync(); } } }
3. 业务逻辑中使用DbContext
通过工厂创建DbContext实例,先执行异步初始化再进行数据库操作:
public class SampleService { private readonly IDbContextFactory<CustomDbContext> _dbContextFactory; public SampleService(IDbContextFactory<CustomDbContext> dbContextFactory) { _dbContextFactory = dbContextFactory; } public async Task<List<Entity>> GetEntitiesAsync() { using var context = _dbContextFactory.CreateDbContext(); await context.InitializeConnectionAsync(); return await context.Entities.ToListAsync(); } }
方案二:中间件提前缓存连接字符串
1. 编写租户中间件
在请求生命周期早期异步获取连接字符串,缓存到HttpContext.Items中:
public class TenantConnStrMiddleware { private readonly RequestDelegate _next; public TenantConnStrMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, ITenantService tenantService) { var connStr = await tenantService.GetConnectionStringAsync(); context.Items["TenantConnectionString"] = connStr; await _next(context); } } // Program.cs 注册中间件(需放在路由、控制器中间件之前) app.UseMiddleware<TenantConnStrMiddleware>();
2. 调整DbContext配置
从HttpContext.Items中读取已缓存的连接字符串完成配置:
builder.Services.AddDbContext<IDbContext, CustomDbContext>((services, options) => { var httpContextAccessor = services.GetRequiredService<IHttpContextAccessor>(); var connStr = httpContextAccessor.HttpContext?.Items["TenantConnectionString"] as string; if (string.IsNullOrEmpty(connStr)) throw new InvalidOperationException("租户连接字符串未完成初始化"); options.UseSqlServer(connStr, x => x.UseHierarchyId().UseNetTopologySuite()) .ConfigureWarnings(x => x.Ignore(CoreEventId.PossibleIncorrectRequiredNavigationWithQueryFilterInteractionWarning)); });
关键注意事项
- 方案一的
DbContextFactory是EF Core 3.0+特性,能更好地适配异步场景,避免DI容器管理DbContext的生命周期冲突。 - 方案二需确保中间件在所有依赖DbContext的组件前执行,保证连接字符串提前就绪。
- 建议为租户连接字符串添加内存/分布式缓存,减少外部API调用频次,提升系统性能。
- 需处理外部API调用失败、租户标识缺失等异常场景,避免请求崩溃。
内容的提问来源于stack exchange,提问作者skysurfer
相关产品推荐
相关产品推荐

