如何解决ASP.NET Core Web API中Dapper工作单元(UnitOfWork)事务错误
解决Dapper工作单元模式下的Transaction未初始化异常
嘿,我一眼就看出问题所在了!你遇到的这个System.InvalidOperationException是因为你的数据库连接已经处于一个挂起的本地事务中,但执行查询命令时没有把事务关联到命令上。下面一步步给你讲清楚怎么修复:
问题根源
你的UnitOfWork已经在构造函数里开启了事务:
_transaction = _connection.BeginTransaction();
并且把这个事务传给了CityRepository,但在CityRepository.All()方法里调用QueryAsync时,只用到了Connection,没有把Transaction传递给Dapper的查询命令。当连接处于事务状态时,所有执行的命令必须关联到这个事务,否则就会抛出这个错误。
修复步骤
1. 给GenericRepository添加Transaction属性
首先确保你的基类GenericRepository<T>封装好事务引用,这样所有子类都能直接使用:
public abstract class GenericRepository<T> { protected IDbConnection Connection { get; } protected IDbTransaction Transaction { get; } protected GenericRepository(IDbTransaction transaction) { Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); Connection = transaction.Connection ?? throw new InvalidOperationException("事务未关联有效连接"); } }
2. 修改CityRepository的All方法,传入Transaction
现在在CityRepository的查询里,把事务作为参数传给Dapper的QueryAsync:
internal class CityRepository : GenericRepository<City>, ICityRepository { public CityRepository(IDbTransaction transaction) : base(transaction) { } public async Task<IEnumerable<City>> All() { // 关键:添加transaction参数关联当前事务 var model = await Connection.QueryAsync<City>( "SELECT * FROM DT_Inspection.City", transaction: Transaction ); return model.ToList(); } }
3. 完善UnitOfWork的IDisposable实现
你的UnitOfWork目前没有正确释放资源,这会导致连接和事务泄漏。实现IDisposable接口来确保资源被正确清理:
public class UnitOfWork : IUnitOfWork, IDisposable { public IConfiguration configuration; private IDbTransaction _transaction; private IDbConnection _connection; ICityRepository _cityRepository; private bool _disposed; public UnitOfWork(IConfiguration _configuration) { configuration = _configuration; _connection = new SqlConnection(_configuration.GetConnectionString("DefaultConnection")); _connection.Open(); _transaction = _connection.BeginTransaction(); } public ICityRepository Cities { get { return _cityRepository ?? (_cityRepository = new CityRepository(_transaction)); } } public void Commit() { try { _transaction.Commit(); } catch { _transaction.Rollback(); throw; } finally { _transaction.Dispose(); _transaction = _connection.BeginTransaction(); resetRepositories(); } } private void resetRepositories() { _cityRepository = null; // 如果有其他仓库,也要在这里重置为null } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 先释放事务,再释放连接 _transaction?.Dispose(); _connection?.Dispose(); } _disposed = true; } ~UnitOfWork() { Dispose(false); } }
4. 注册正确的生命周期
在ASP.NET Core的依赖注入中,把IUnitOfWork注册为Scoped生命周期,这样每个HTTP请求对应一个UnitOfWork实例,请求结束后自动释放:
// 在Program.cs中 builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
为什么这样能解决问题?
通过把事务传递给Dapper的查询命令,相当于告诉数据库:“这个查询属于当前正在运行的事务”,这样连接就不会因为命令未关联事务而抛出异常了。同时完善的资源释放逻辑能避免连接泄漏,保证每个请求的资源都被正确清理。
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

