导读:本期聚焦于小伙伴创作的《Dapper如何处理事务?Dapper Transaction使用教程与常见误区》,敬请观看详情。在批量写入订单和扣减库存的接口里,若不用事务,中途异常会导致数据不一致。Dapper本身不提供独立事务对象,而是依赖IDbTransaction。通过connection.BeginTransaction拿到事务后,把同一个事务传给多次Execute或Query,就能保证多句SQL要么全成功要么全回滚。很多人误以为Dapper有自带事务方法,其实它只是对ADO.NET的轻量封装。本教程演示如何用Unit of Work思想组织代码,并说明在异步方法中如何用BeginTransactionAsync避免死锁,以及嵌套事务在多数关系库中的真实表现。

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

Dapper如何处理事务?Dapper Transaction使用教程与常见误区

一、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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。