C#中多方法间使用Dapper事务,如何避免传递连接与事务参数
无需传递SqlConnection/SqlTransaction的Dapper事务实现方案
方案1:类级字段存储连接与事务
在业务类中添加私有字段临时存储当前的连接和事务,开启事务时赋值,操作方法优先使用这些字段,事务结束后重置状态。
public class OrderItemService { private SqlConnection _currentConnection; private SqlTransaction _currentTransaction; public async Task<bool> OrderItemChange(int orderItemId) { _currentConnection = await Connection.GetOpenConnection(); _currentTransaction = _currentConnection.BeginTransaction(); try { await Find(orderItemId); await Remove(orderItemId); await Save(orderItemId); _currentTransaction.Commit(); return true; } catch (Exception ex) { _currentTransaction.Rollback(); return false; } finally { _currentTransaction?.Dispose(); _currentConnection?.Dispose(); // 重置字段避免后续方法误用 _currentConnection = null; _currentTransaction = null; } } private async Task Find(int orderItemId) { // 优先使用类内的连接和事务,无则创建新连接 var conn = _currentConnection ?? await Connection.GetOpenConnection(); var trans = _currentTransaction; var item = await conn.QueryFirstOrDefaultAsync<OrderItem>( "SELECT * FROM OrderItems WHERE Id = @Id", new { Id = orderItemId }, transaction: trans); // ...业务逻辑 } // Remove和Save方法同理,复用conn和trans字段 }
优缺点:实现简单,适配单一类内的事务场景;但类的状态会被事务方法临时污染,必须在finally中重置,多线程/异步并发场景需谨慎。
方案2:AsyncLocal实现隐式事务上下文
用AsyncLocal<T>存储当前异步上下文的连接和事务,同一调用链上的所有方法可自动获取,无需手动传参。
public static class TransactionContext { private static readonly AsyncLocal<(SqlConnection Conn, SqlTransaction Trans)> _context = new(); public static void Set(SqlConnection conn, SqlTransaction trans) { _context.Value = (conn, trans); } public static (SqlConnection Conn, SqlTransaction Trans) Get() { return _context.Value; } public static void Clear() { _context.Value = default; } } public class OrderItemService { public async Task<bool> OrderItemChange(int orderItemId) { using var conn = await Connection.GetOpenConnection(); using var trans = conn.BeginTransaction(); TransactionContext.Set(conn, trans); try { await Find(orderItemId); await Remove(orderItemId); await Save(orderItemId); trans.Commit(); return true; } catch (Exception ex) { trans.Rollback(); return false; } finally { TransactionContext.Clear(); } } private async Task Find(int orderItemId) { var (conn, trans) = TransactionContext.Get(); // 上下文无连接时,说明不在事务中,创建新连接 if (conn == null) { conn = await Connection.GetOpenConnection(); trans = null; } var item = await conn.QueryFirstOrDefaultAsync<OrderItem>( "SELECT * FROM OrderItems WHERE Id = @Id", new { Id = orderItemId }, transaction: trans); // ...业务逻辑 } }
优缺点:支持跨类的事务传递,异步上下文安全;需要额外的上下文类,复杂度稍高,但复用性强。
方案3:使用TransactionScope(.NET Core 2.1+)
TransactionScope可自动管理事务,Dapper会自动识别当前事务范围,无需手动传递事务参数。异步场景下需指定TransactionScopeAsyncFlowOption.Enabled。
public async Task<bool> OrderItemChange(int orderItemId) { // 开启异步流支持,避免事务上下文丢失 using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); try { await Find(orderItemId); await Remove(orderItemId); await Save(orderItemId); scope.Complete(); return true; } catch (Exception ex) { // 不调用Complete则自动回滚 return false; } } private async Task Find(int orderItemId) { using var conn = await Connection.GetOpenConnection(); // Dapper自动关联当前TransactionScope var item = await conn.QueryFirstOrDefaultAsync<OrderItem>( "SELECT * FROM OrderItems WHERE Id = @Id", new { Id = orderItemId }); // ...业务逻辑 }
优缺点:代码最简洁,无需修改原有方法;但如果操作涉及多个数据库会触发分布式事务(MSDTC),可能带来性能开销,需根据场景评估。
内容的提问来源于stack exchange,提问作者Farzad Karimi
相关产品推荐
相关产品推荐

