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引用。
修复方案
- 统一生命周期:将
IUnitOfWork改为Scoped注册,确保同一个请求内,工作单元、连接、事务共享同一个实例:
services.AddScoped<IUnitOfWork, UnitOfWork>();
- 重构工作单元,统一管理连接与事务:移除单独的
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(); } }
确保仓储依赖正确传递:保持仓储基类的构造函数不变,由
UnitOfWork统一注入同一个连接和事务,避免DI容器为不同仓储创建不同的连接/事务实例。验证异步操作作用域:确保所有异步操作都在当前请求的作用域内执行,若涉及后台任务,需手动创建
IServiceScope来管理对象生命周期。
内容的提问来源于stack exchange,提问作者Reza Shirazi
相关产品推荐
相关产品推荐

