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

ASP.NET Core 6中Dapper执行ExecuteAsync后SqlConnection或事务为空

问题描述

在ASP.NET Core 6 Web API应用中使用Dapper,通过依赖注入注入SqlConnection并开启事务,首次插入正常,但第二次插入时_sqlConnection和_dbTransaction变量均变为null,相关配置及代码如下:

Startup.cs 配置

services.AddScoped((s) => new SqlConnection(Configuration["APPSETTINGS_SQL_CONNECTION_STRING"]));

services.AddScoped<IDbTransaction>((s) =>
            {
                var connection = s.GetRequiredService<SqlConnection>();
                var tokenService = s.GetRequiredService<ITokenService>();
                connection.AccessToken = tokenService.GetAccessToken();
                connection.Open();
                return connection.BeginTransaction();
            });
services.AddTransient<ITokenService, TokenService>();
services.AddTransient<IUnitOfWork, UnitOfWork>();

RepositoryBase.cs 仓储基类

public RepositoryBase(IDbTransaction dbTransaction, SqlConnection sqlConnection)
{
    _dbTransaction = dbTransaction;
    _sqlConnection = sqlConnection;         
}
    
private async Task<int> CreateEntity(T entity, string sql)
{
    return await _sqlConnection.ExecuteAsync(sql, transaction: _dbTransaction);
}

Service.cs 业务服务代码

var workerMappingTask = _unitOfWork.MappingRepository.CreateAsync(
                        WorkerMappingEntity.Create(1,"test","12"));
    
var workerConsentTask = _unitOfWork.WorkerRepository.CreateAsync(100,"test","123","123");
    
await Task.WhenAll(new Task[] { workerMappingTask, workerConsentTask });

_unitOfWork.Commit();
问题排查与解决

核心原因

  • 生命周期不匹配:IUnitOfWork注册为Transient(每次请求都创建新实例),但SqlConnection和IDbTransaction是Scoped(请求内唯一)。异步并行操作时,可能导致工作单元引用的连接/事务被提前释放,或不同仓储拿到的实例不一致。
  • 事务注入逻辑缺陷:单独注册IDbTransaction时,每次获取事务都会触发连接打开和事务创建,但DI容器无法保证多个仓储拿到的事务属于同一个连接,易出现实例不一致问题。
  • 异步操作对象回收:Scoped对象在请求结束时会自动释放,若异步操作未在请求作用域内完成,会导致连接/事务实例被回收,出现null引用。

修复方案

  1. 统一生命周期:将IUnitOfWork改为Scoped注册,确保同一个请求内,工作单元、连接、事务共享同一个实例:
services.AddScoped<IUnitOfWork, UnitOfWork>();
  1. 重构工作单元,统一管理连接与事务:移除单独的IDbTransaction注册,在UnitOfWork内部创建并管理连接和事务,确保所有仓储使用同一个实例:
public class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly SqlConnection _sqlConnection;
    private IDbTransaction _dbTransaction;

    public IMappingRepository MappingRepository { get; }
    public IWorkerRepository WorkerRepository { get; }

    public UnitOfWork(SqlConnection sqlConnection, ITokenService tokenService)
    {
        _sqlConnection = sqlConnection;
        // 初始化连接与事务
        _sqlConnection.AccessToken = tokenService.GetAccessToken();
        _sqlConnection.Open();
        _dbTransaction = _sqlConnection.BeginTransaction();

        // 向仓储传入同一个事务和连接
        MappingRepository = new MappingRepository(_dbTransaction, _sqlConnection);
        WorkerRepository = new WorkerRepository(_dbTransaction, _sqlConnection);
    }

    public void Commit()
    {
        try
        {
            _dbTransaction.Commit();
        }
        catch
        {
            _dbTransaction.Rollback();
            throw;
        }
    }

    public void Dispose()
    {
        _dbTransaction?.Dispose();
        _sqlConnection?.Close();
        _sqlConnection?.Dispose();
    }
}
  1. 确保仓储依赖正确传递:保持仓储基类的构造函数不变,由UnitOfWork统一注入同一个连接和事务,避免DI容器为不同仓储创建不同的连接/事务实例。

  2. 验证异步操作作用域:确保所有异步操作都在当前请求的作用域内执行,若涉及后台任务,需手动创建IServiceScope来管理对象生命周期。


内容的提问来源于stack exchange,提问作者Reza Shirazi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 06:44:57