You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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直接使用配置好的连接,更符合依赖注入原则:

  1. 注册带连接字符串的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]);
});
  1. 修改基类,直接注入依赖而非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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:51:14