如何在C# .NET 6中实现运行时切换连接字符串的DbContext依赖注入?
动态多数据库场景下的DbContext依赖注入方案
针对你描述的「根据请求client_id动态获取连接字符串并创建对应DbContext」的场景,核心思路是放弃启动时固定注入DbContext,改用工厂模式在请求生命周期内动态创建实例,具体实现步骤如下:
1. 先固定注入参数库DbContext
首先需要一个存储所有客户端连接字符串的参数数据库,这个库的连接是固定的,直接在启动时注入:
参数库实体与DbContext定义
// 存储客户端连接信息的实体 public class ClientConnection { public string ClientId { get; set; } public string ConnectionString { get; set; } } // 参数库DbContext public class ClientConfigDbContext : DbContext { public ClientConfigDbContext(DbContextOptions<ClientConfigDbContext> options) : base(options) { } public DbSet<ClientConnection> ClientConnections { get; set; } }
启动时注入参数库DbContext
// Program.cs builder.Services.AddDbContext<ClientConfigDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("ClientConfigDb")));
2. 定义业务DbContext与动态工厂
业务数据库结构统一,所以业务DbContext的定义保持常规,重点实现一个工厂类,负责根据client_id获取连接字符串并创建对应的DbContext实例:
业务DbContext定义
public class BusinessDbContext : DbContext { public BusinessDbContext(DbContextOptions<BusinessDbContext> options) : base(options) { } // 你的业务实体集合 public DbSet<Order> Orders { get; set; } public DbSet<User> Users { get; set; } }
动态DbContext工厂实现
public interface IBusinessDbContextFactory { Task<BusinessDbContext> CreateAsync(string clientId); } public class BusinessDbContextFactory : IBusinessDbContextFactory { private readonly ClientConfigDbContext _configDb; private readonly IMemoryCache _cache; private readonly IDbContextFactory<BusinessDbContext> _dbFactory; public BusinessDbContextFactory(ClientConfigDbContext configDb, IMemoryCache cache, IDbContextFactory<BusinessDbContext> dbFactory) { _configDb = configDb; _cache = cache; _dbFactory = dbFactory; } public async Task<BusinessDbContext> CreateAsync(string clientId) { // 优先从缓存取连接字符串,避免频繁查询参数库 var connStr = await _cache.GetOrCreateAsync($"ClientConn_{clientId}", async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); var client = await _configDb.ClientConnections .FirstOrDefaultAsync(c => c.ClientId == clientId); if (client == null) throw new KeyNotFoundException($"未找到客户端 {clientId} 的连接配置"); return client.ConnectionString; }); // 构建DbContext选项并创建实例 var options = new DbContextOptionsBuilder<BusinessDbContext>() .UseSqlServer(connStr) .Options; return new BusinessDbContext(options); } }
启动时注入工厂
// Program.cs // 注入EF核心的DbContext工厂,用于动态创建实例 builder.Services.AddDbContextFactory<BusinessDbContext>(); // 注入自定义的业务DbContext工厂 builder.Services.AddScoped<IBusinessDbContextFactory, BusinessDbContextFactory>(); // 注入缓存(可选,用于优化连接字符串查询) builder.Services.AddMemoryCache();
3. 在API控制器中使用
直接注入工厂,根据请求的client_id动态获取对应的DbContext:
[ApiController] [Route("api/[controller]")] public class OrdersController : ControllerBase { private readonly IBusinessDbContextFactory _dbFactory; public OrdersController(IBusinessDbContextFactory dbFactory) { _dbFactory = dbFactory; } [HttpGet] public async Task<IActionResult> GetOrders([FromQuery] string client_id) { try { using var dbContext = await _dbFactory.CreateAsync(client_id); var orders = await dbContext.Orders.ToListAsync(); return Ok(orders); } catch (KeyNotFoundException ex) { return NotFound(ex.Message); } } }
额外优化:通过中间件自动提取client_id
如果所有请求都需要携带client_id,可以用中间件将其存入HttpContext,避免在每个接口方法中手动接收:
中间件实现
public class ClientIdMiddleware { private readonly RequestDelegate _next; public ClientIdMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { if (!context.Request.Query.TryGetValue("client_id", out var clientId) || string.IsNullOrEmpty(clientId)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("请求必须携带有效的client_id参数"); return; } context.Items["CurrentClientId"] = clientId.ToString(); await _next(context); } } // Program.cs中注册中间件(放在路由之前) app.UseMiddleware<ClientIdMiddleware>();
修改工厂自动获取client_id
public class BusinessDbContextFactory : IBusinessDbContextFactory { private readonly ClientConfigDbContext _configDb; private readonly IMemoryCache _cache; private readonly IHttpContextAccessor _httpContextAccessor; public BusinessDbContextFactory(ClientConfigDbContext configDb, IMemoryCache cache, IHttpContextAccessor httpContextAccessor) { _configDb = configDb; _cache = cache; _httpContextAccessor = httpContextAccessor; } public async Task<BusinessDbContext> CreateAsync() { var clientId = _httpContextAccessor.HttpContext?.Items["CurrentClientId"] as string; if (string.IsNullOrEmpty(clientId)) throw new InvalidOperationException("无法从请求中获取client_id"); // 后续逻辑同之前的CreateAsync方法 var connStr = await _cache.GetOrCreateAsync($"ClientConn_{clientId}", async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); var client = await _configDb.ClientConnections .FirstOrDefaultAsync(c => c.ClientId == clientId); if (client == null) throw new KeyNotFoundException($"未找到客户端 {clientId} 的连接配置"); return client.ConnectionString; }); var options = new DbContextOptionsBuilder<BusinessDbContext>() .UseSqlServer(connStr) .Options; return new BusinessDbContext(options); } }
控制器简化调用
[HttpGet] public async Task<IActionResult> GetOrders() { try { using var dbContext = await _dbFactory.CreateAsync(); var orders = await dbContext.Orders.ToListAsync(); return Ok(orders); } catch (KeyNotFoundException ex) { return NotFound(ex.Message); } }
内容的提问来源于stack exchange,提问作者JaviScript
相关产品推荐
相关产品推荐

