如何在dapper.net中使用事务?

157

我想在多个表上运行多个插入语句。我正在使用 dapper.net。 我没有看到任何处理 dapper.net 事务的方法。

请分享您如何在 dapper.net 中使用事务的想法。

6个回答

135

以下是代码片段:

using System.Transactions;    
....    
using (var transactionScope = new TransactionScope())
{
    DoYourDapperWork();
    transactionScope.Complete();
}

请注意,由于默认情况下没有引用System.Transactions程序集,因此您需要添加对它的引用。


8
在出错时是否需要显式回滚,或者 System.Transactions 是否会自动处理? - Norbert Norbertson
11
它会自动执行,在 Dispose() 方法中。如果没有调用 Complete(),事务将被回滚。 - the_joric
7
需要提醒的是,由于另一个答案(https://dev59.com/cmkv5IYBdhLWcg3wwjkI#20047975)的原因,如果您选择这个答案,连接必须在`TransactionScope`代码块中打开。 - 0x49D1
4
请参阅(https://dev59.com/cmkv5IYBdhLWcg3wwjkI#20047975)- DoYouDapperWork(Execute、Query等)需要在参数中包含事务。 - Matthieu
2
如果您的 DoYourDapperWork() 使用多个 SqlConnection 来执行工作,此方法是否有效?例如,假设我有一个 dapper 存储库,每个方法使用一个新连接。我可以调用其中几个方法并将其包装在 TransactionScope 中吗? - Ε Г И І И О
显示剩余2条评论

126

我更喜欢通过直接从连接获取交易来使用更直观的方法:

// This called method will get a connection, and open it if it's not yet open.
using (var connection = GetOpenConnection())
using (var transaction = connection.BeginTransaction())
{
    connection.Execute(
        "INSERT INTO data(Foo, Bar) values (@Foo, @Bar);", listOf5000Items, transaction);
    transaction.Commit();
}

@ANeves:嗯,我们可能在使用不同的Dapper框架,因为这个项目有:https://github.com/StackExchange/dapper-dot-net - andrecarlucci
37
在执行 .begintransaction 前必须先调用 connection.open()。 - Timeless
1
除非在TransactionScope中打开连接,否则连接不会自动注册。我不知道你的代码是如何工作的,如果GetOpenConnection在TransactionScope内部以某种神奇的方式自动打开,但我敢打赌它不会这样做。 - Erik Bergstedt
@ErikBergstedt,您是在说连接必须在我们对其调用.BeginTransaction()之后才能打开吗?如果是这样的话,这个扩展方法将促进错误的事务使用。(在我看来,它甚至应该抛出“无法在连接已经打开后打开事务”。) - ANeves
4
在“执行”函数中将交易作为参数包含是个好主意,因为这是必需的。 - Arve Systad

65

Dapper有三种处理事务的方式:

  1. 简单事务
  2. 使用事务范围的事务
  3. 使用Dapper事务(额外的nuget包 这是最受欢迎的方法

你可以从官方教程网站这里了解更多关于这些事务方式的信息。

以下是对各事务方式的详细介绍:

1. 简单事务

在此示例中,您在现有的数据库连接上创建一个事务,然后将该事务传递给dapper的Execute方法(这是一个可选参数)。

完成所有工作后,只需提交事务即可。

string sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";

using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
    connection.Open();
    
    using (var transaction = connection.BeginTransaction())
    {
        connection.Execute(sql, new {CustomerName = "Mark"}, transaction: transaction);
        connection.Execute(sql, new {CustomerName = "Sam"}, transaction: transaction);
        connection.Execute(sql, new {CustomerName = "John"}, transaction: transaction);
        
        transaction.Commit();
    }
}

2. 事务来自事务范围

如果您想创建一个事务范围,您需要在创建数据库连接之前执行此操作。一旦创建了事务范围,您可以执行所有操作,然后进行单个调用以完成事务,这将提交所有命令。

using (var transaction = new TransactionScope())
{
    var sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";

    using (var connection = My.ConnectionFactory())
    {
        connection.Open();

        connection.Execute(sql, new {CustomerName = "Mark"});
        connection.Execute(sql, new {CustomerName = "Sam"});
        connection.Execute(sql, new {CustomerName = "John"});
    }

    transaction.Complete();
}

3. 使用 Dapper 事务

在我看来,这是实现代码中事务最为方便易读的方法。Dapper 提供了一个扩展的 SQL 事务实现,称之为 Dapper 事务(可以在此处找到),它使你能够直接运行 SQL 执行而不需要使用事务。

string sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";

using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
    connection.Open();
    
    using (var transaction = connection.BeginTransaction())
    {
        transaction.Execute(sql, new {CustomerName = "Mark"});
        transaction.Execute(sql, new {CustomerName = "Sam"});
        transaction.Execute(sql, new {CustomerName = "John"});

        transaction.Commit();
    }
}

4
这是正确的答案。我很惊讶我不得不找到最后一个答案才找到它。其他答案是“不要使用Dapper”,或者“不要使用事务”。这解释了Dapper应该如何使用事务的方式,以及Dapper打算我们如何使用事务。额外奖励:现在我看到了,Dapper在IDbTransaction上添加Execute扩展方法是一个巨大的创意。 - Ian Boyd
2
每当我需要一个子方法来执行数据库工作时,我总是传递IDbConnectionIDbTransaction。通常你只需要传递IDbConnection,但如果你也在事务中,你就必须同时传递IDbTransaction。直到现在我才意识到IDbTransaction包含了它来自的IDbConnection。所以现在我明白了25年前设计ADO.net接口的微软开发人员的想法——只传递IDbTransaction - Ian Boyd
3
@IanBoyd 很高兴这个答案能够帮到你。我觉得我来晚了一点,但是我想分享一下我最近使用 Dapper 的理解和经验。 - Newteq Developer
1
嗨@Newteq,第三种方式 - 使用Dapper事务 - 是否应该有try/catch和transaction.Rollback()? - Leszek P
1
这很公平。我没有考虑到那一点。我会相应地更新答案。 - Newteq Developer
显示剩余3条评论

21

由于Dapper仅运行ADO.NET命令,因此您应该能够使用TransactionScope

using (var scope = new TransactionScope())
{
   // open connection
   // insert
   // insert
   scope.Complete();
}

14
考虑到您的所有表都在单个数据库中,我不同意一些答案中提出的使用 TransactionScope 的解决方案。请参见此答案
  1. TransactionScope 通常用于分布式事务;跨多个数据库的事务可能在不同的系统上进行。这需要对操作系统和 SQL Server 进行一些配置,否则将无法工作。如果您的所有查询针对单个数据库,则不建议使用此方法。
    但是,对于单个数据库,当您需要将代码包含在不受控制的事务中时,它可能会有用。在单个数据库中,也不需要特殊配置。

  2. connection.BeginTransaction 是 ADO.NET 语法,在单个数据库中实现事务(在 C#、VB.NET 等语言中)。它不能跨多个数据库工作。

因此,connection.BeginTransaction() 是更好的选择。
实现 UnitOfWork 是处理事务的更好方式,如此答案所述。

8
不需要多个数据库即可从TransactionScope中受益。它在环境上特别有用。将其用于包装您不拥有或无法修改的代码以进行事务处理非常有效。例如,当单元/集成测试调用数据库并希望回滚后,可以在其中发挥很大作用。只需添加一个TransactionScope,测试代码,并在测试清理期间进行处置。 - Larry Smith
4
@LarrySmith: 同意,但问题不涉及这些。原帖只是说他想在一个事务中插入多个表中的数据。一些答案包括被采纳的答案,建议使用TransactionScope,但对于原帖想要的效果来说,这种方法是低效的。我同意TransactionScope在许多情况下都是好工具,但不适用于这种情况。 - Amit Joshi

6

Daniel的回答对我来说效果很好。为了完整起见,这里有一个片段展示如何使用事务范围和dapper进行提交和回滚:

using System.Transactions;
    // _sqlConnection has been opened elsewhere in preceeding code 
    using (var transactionScope = new TransactionScope())
    {
        try
        {
            long result = _sqlConnection.ExecuteScalar<long>(sqlString, new {Param1 = 1, Param2 = "string"});

            transactionScope.Complete();
        }
        catch (Exception exception)
        {
            // Logger initialized elsewhere in code
            _logger.Error(exception, $"Error encountered whilst executing  SQL: {sqlString}, Message: {exception.Message}")

            // re-throw to let the caller know
            throw;
        }
    } // This is where Dispose is called 

3
这归结于个人偏好。我更喜欢知道第一次出现问题的时间,并且不认为日志语句是无用的。此外,我的回答通过演示使用Dapper处理事务的一种方式仍然有帮助。 - Sudhanshu Mishra
@CodeNaked,首先,你的顺序错了。如果有异常,catch块会首先被触发,然后是using语句块的结束。其次,请查看此答案和引用的MSDN文档:https://dev59.com/DW435IYBdhLWcg3woRnG#5306896 调用dispose第二次并不会有害,一个设计良好的对象会忽略第二个调用。这个负评是不合理的! - Sudhanshu Mishra
@dotnetguy - 我并不是试图沟通哪个 Dispose 方法先调用,哪个后调用,只是它被调用了两次。至于“第二次调用dispose不会有害”,那是一个很大的假设。我已经学到了文档和实际实现经常不一致的教训。但如果你想听微软的话:https://msdn.microsoft.com/zh-cn/library/ms182334.aspx?f=255&MSPPError=-2147217396 - CodeNaked
4
那么,你下投票的原因是代码分析警告?这并不意味着答案是错误或误导性的 - 那时下投票是恰当的。为什么不编辑答案,提出一个更好的解决方案,同时保留功能呢?Stack Overflow 的宗旨在于帮助和建设性批评。 - Sudhanshu Mishra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接