在Server-Side Blazor中使用IDbConnection是否需要依赖注入?
针对Server-Side Blazor中IDbConnection管理的优化建议
为什么Transient/Scoped注册没明显区别?
你的问题核心在于当前代码的实现方式抵消了DI生命周期的作用:
- MediatR的请求处理器(Handler)默认是Transient,每次处理请求都会新建一个Handler实例。
- 你在Handler的构造函数中通过
IServiceProvider获取IDbConnection后,会重新设置连接字符串。如果用Scoped注册连接,同一个用户电路内的所有Handler会共用同一个连接实例,但每次Handler初始化都覆盖连接字符串,不仅会引发并发冲突,还会让连接实例的复用失去意义,效果和Transient没区别,甚至更危险。 - 另外,Server-Side Blazor的Scoped是绑定用户会话(电路)的,长期持有数据库连接会耗尽连接池,本身就不适合用来注册SqlConnection。
直接在基类创建SqlConnection的利弊
- 优点:完全控制连接生命周期,避免DI带来的配置冲突和生命周期不匹配问题,实现简单直接。
- 缺点:代码重复,无法统一管理连接配置(比如后续换数据库类型要改多处),单元测试时难以Mock数据库连接,需要手动确保连接释放。
推荐的优化方案
方案1:改进DI注册,避免在Handler中设置连接字符串
把连接字符串的配置逻辑放到DI注册阶段,Handler直接使用配置好的连接,更符合依赖注入原则:
- 注册带连接字符串的IDbConnection(支持默认和多连接场景):
// 默认连接注册 services.AddTransient<IDbConnection>(sp => { var config = sp.GetRequiredService<IConfiguration>(); return new SqlConnection(config[ConnectionConstants.AzureSql]); }); // 多连接场景:注册连接工厂 services.AddTransient<Func<string, IDbConnection>>(sp => { var config = sp.GetRequiredService<IConfiguration>(); return connectionStringId => new SqlConnection(config[connectionStringId]); });
- 修改基类,直接注入依赖而非IServiceProvider:
public class SQLHandlerBase<T> { protected readonly IDbConnection _dbConnection; protected readonly ILogger _log; // 默认连接版本 public SQLHandlerBase(IDbConnection dbConnection, ILogger<T> logger) { _dbConnection = dbConnection; _log = logger; } // 多连接版本 public SQLHandlerBase(Func<string, IDbConnection> connectionFactory, ILogger<T> logger, string connectionStringId = null) { var connId = connectionStringId ?? ConnectionConstants.AzureSql; _dbConnection = connectionFactory(connId); _log = logger; } }
这种方式下,DI容器负责创建并配置连接,Handler无需依赖IServiceProvider,每个Transient的Handler会拿到独立的连接实例,配合ADO.NET连接池,性能和安全性都有保障。
方案2:直接创建连接但优化实现(适合不想用DI管理连接的场景)
如果坚持直接创建连接,建议封装连接创建逻辑,确保正确释放:
public class SQLHandlerBase<T> { protected readonly ILogger _log; protected readonly IConfiguration _configuration; private readonly string _connectionStringId; public SQLHandlerBase(IConfiguration configuration, ILogger<T> logger, string connectionStringId = null) { _configuration = configuration; _log = logger; _connectionStringId = connectionStringId ?? ConnectionConstants.AzureSql; } // 封装连接创建方法,方便测试时重写 protected virtual IDbConnection CreateDbConnection() { return new SqlConnection(_configuration[_connectionStringId]); } // Handler中使用示例:用using确保连接自动释放 // public async Task<Result> Handle(Query request, CancellationToken cancellationToken) // { // using var conn = CreateDbConnection(); // var data = await conn.QueryAsync<Model>(sql, request); // return Result.Success(data); // } }
这种方式每次请求创建新连接,用完自动释放,避免长期持有连接,同时保留了单元测试的灵活性(可以重写CreateDbConnection返回Mock实例)。
关键提醒
ADO.NET自带连接池机制,即使每次创建新的SqlConnection实例,底层的数据库连接会被复用,所以不需要刻意复用SqlConnection对象。长期持有连接反而会导致连接池耗尽,是数据库操作的大忌。
内容的提问来源于stack exchange,提问作者Mark Luhring
相关产品推荐
相关产品推荐

