在.NET生态中,Dapper以其轻量和高效深受开发者喜爱。当它面对需要多步数据库操作的业务场景时,事务成为保障数据一致性的核心机制。Dapper并没有重新发明一套事务系统,而是直接建立在ADO.NET的IDbTransaction之上,因此理解清楚底层连接与事务的关系,是用好Dapper事务的前提。

一、Dapper事务的基本用法
Dapper的所有扩展方法,例如Execute、Query、QueryFirstOrDefault等,都提供了一个可选参数transaction,类型正是IDbTransaction。我们只要从同一个已打开的DbConnection中开启一个事务,然后把该事务实例传入每一次调用即可。下面是一段最基础的同步写法,演示在同一个事务里插入用户并写入日志。
using System.Data.SqlClient;
using Dapper;
var connStr = "Server=127.0.0.1;Database=test;User Id=sa;Password=123;";
using var connection = new SqlConnection(connStr);
connection.Open();
using var transaction = connection.BeginTransaction();
try
{
// 插入用户
var userId = connection.ExecuteScalar<int>(
"INSERT INTO Users(Name) VALUES(@Name); SELECT SCOPE_IDENTITY();",
new { Name = "张三" },
transaction);
// 写入日志,使用同一个事务
connection.Execute(
"INSERT INTO Logs(UserId,Action) VALUES(@UserId,@Action)",
new { UserId = userId, Action = "注册" },
transaction);
// 全部成功再提交
transaction.Commit();
}
catch
{
// 任意一步出错则回滚
transaction.Rollback();
throw;
}
上面的代码展示了事务的标准生命周期:开启、执行多步操作、提交或回滚。需要特别注意的是,connection必须处于Open状态才能BeginTransaction,否则会抛出InvalidOperationException。另外,Dapper不会帮你管理事务的提交与回滚,这些动作必须显式调用。
从设计角度看,这种写法把事务边界清晰地暴露在业务代码里。对于简单场景足够直观,但在复杂服务中容易遗漏Rollback或Commit,因此建议结合using语句或封装工作单元来降低风险。
二、异步场景下的事务处理
现代Web接口普遍采用异步编程模型,Dapper也提供了完整的异步API。在异步方法中,应当使用BeginTransactionAsync以及对应的ExecuteAsync、QueryAsync,并配合await关键字。错误的做法是把同步事务和异步命令混用,这可能在高并发下引发连接状态冲突。
using System.Data.SqlClient;
using Dapper;
public async Task CreateOrderAsync(string product, int qty)
{
await using var connection = new SqlConnection(connStr);
await connection.OpenAsync();
await using var tx = await connection.BeginTransactionAsync();
try
{
var productId = await connection.ExecuteScalarAsync<int>(
"SELECT Id FROM Products WHERE Name=@Name",
new { Name = product },
tx);
await connection.ExecuteAsync(
"INSERT INTO Orders(ProductId,Qty) VALUES(@ProductId,@Qty)",
new { ProductId = productId, Qty = qty },
tx);
await connection.ExecuteAsync(
"UPDATE Products SET Stock=Stock-@Qty WHERE Id=@Id",
new { Id = productId, Qty = qty },
tx);
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
}
异步事务和同步事务在语义上完全一致,只是调用链全部改为await。一个常见误区是认为await using会自动回滚,实际上如果CommitAsync之前发生异常且未捕获,事务会随着连接释放而被数据库隐式回滚,但这属于不可控行为,显式RollbackAsync更稳妥。
在压力测试中,异步事务能显著提升吞吐量,因为线程在等待数据库响应时不会被阻塞。但要注意连接池配置,若事务持有时间过长,可能造成连接耗尽。
三、使用工作单元封装事务
为了让Dapper事务在业务层更好维护,可以引入工作单元(Unit of Work)模式。核心思路是:由工作单元统一持有连接和事务,仓储对象只负责拼接SQL,最后由工作单元决定提交或回滚。
public class DapperUnitOfWork : IDisposable
{
private readonly IDbConnection _connection;
private readonly IDbTransaction _transaction;
public DapperUnitOfWork(string connStr)
{
_connection = new SqlConnection(connStr);
_connection.Open();
_transaction = _connection.BeginTransaction();
}
public IDbConnection Connection => _connection;
public IDbTransaction Transaction => _transaction;
public void Commit() => _transaction.Commit();
public void Rollback() => _transaction.Rollback();
public void Dispose()
{
_transaction?.Dispose();
_connection?.Dispose();
}
}
// 业务中使用
using var uow = new DapperUnitOfWork(connStr);
uow.Connection.Execute("INSERT INTO A VALUES(1)", null, uow.Transaction);
uow.Connection.Execute("INSERT INTO B VALUES(2)", null, uow.Transaction);
uow.Commit();
这种封装把事务边界收敛到工作单元内部,业务代码不必反复书写BeginTransaction和Rollback。同时,多个仓储可以共享同一个uow实例,自然形成跨表的事务一致性。
当然,工作单元也不是银弹。在分布式系统中,单库事务无法跨服务,此时需要引入消息队列或Saga模式。但对于单体架构下的强一致需求,Dapper加工作单元已经足够简洁可靠。
四、常见误区与注意事项
第一个误区是以为Dapper有独立的事务类。实际上Dapper只是把IDbTransaction作为参数透传,真正的隔离级别、锁机制都由数据库驱动控制。若需要设置隔离级别,应在BeginTransaction时指定,例如connection.BeginTransaction(IsolationLevel.ReadCommitted)。
第二个误区是嵌套事务。在SQL Server中,所谓嵌套事务只是通过@@TRANCOUNT计数,只有最外层提交才真正生效,内层回滚会全部撤销。因此不要在Dapper里尝试用多个BeginTransaction模拟子事务,这往往带来诡异的回滚行为。
| 场景 | 推荐做法 | 风险点 |
|---|---|---|
| 单库多表写入 | 同一IDbTransaction传入多次Execute | 漏写Rollback导致脏数据 |
| 高并发异步接口 | BeginTransactionAsync加await | 连接池耗尽 |
| 跨服务数据一致 | 本地事务加补偿机制 | 误用单库事务期望分布式生效 |
最后提醒,事务作用域内应尽量只放必要的SQL,避免远程调用或耗时计算,否则会长时间占用数据库连接,拖垮整体性能。
五、小结
Dapper处理事务的本质就是用好ADO.NET的IDbTransaction。无论同步还是异步,核心都是开启事务、复用同一事务对象执行命令、最后提交或回滚。通过工作单元模式可以让代码更整洁,而避开嵌套事务误区则能减少线上故障。掌握这些要点后,你完全可以用Dapper写出安全且高效的数据访问层。
DapperTransactionIDbTransaction修改时间:2026-08-06 09:09:36