【问题标题】:How to use transactions with dapper.net?如何使用 dapper.net 进行交易?
【发布时间】:2012-05-08 23:41:06
【问题描述】:

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

请分享您对如何使用 dapper.net 进行交易的想法。

【问题讨论】:

    标签: c# transactions dapper


    【解决方案1】:

    这里是代码sn-p:

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

    请注意,您需要添加对System.Transactions 程序集的引用,因为默认情况下不引用它。

    【讨论】:

    • 是否有必要明确回滚错误或 System.Transactions 自动处理?
    • @NorbertNorbertson 它在Dispose() 方法中自动完成。如果Complete()没有被调用,事务被回滚。
    • 由于另一个答案 (stackoverflow.com/a/20047975/47672) 值得一提:如果您选择此答案,则必须使用块在 TransctionScope 内打开连接。
    • 另见 (stackoverflow.com/a/20047975/444469) - DoYouDapperWork(执行、查询等)需要参数中的事务。
    • 如果您的 DoYourDapperWork() 使用多个 SqlConnections 来完成这项工作,这是否有效?例如,假设我有一个小巧的存储库,其中每个方法都使用一个新连接。我可以调用其中几个包装了 TransactionScope 吗?
    【解决方案2】:

    我更喜欢使用更直观的方法,直接从连接中获取交易:

    // 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 框架,因为这个有:github.com/StackExchange/dapper-dot-net
    • 必须在 .begintransaction 之前调用 connection.open()
    • 除非您在事务范围内打开连接,否则连接不会自动加入事务范围。我不知道你的代码是如何工作的,如果 GetOpenConnection 以某种方式在事务范围内神奇地打开了自己,但我敢打赌它不会
    • @ErikBergstedt,您是说连接必须只有在我们调用.BeginTransaction() 之后才能打开?如果是这种情况,这种扩展方法会促进事务的错误使用。 (IMO,它甚至应该抛出“连接已经打开后无法打开事务”。)
    • 最好将事务作为参数包含在Execute 中,因为这是必需的。
    【解决方案3】:

    你应该可以使用TransactionScope,因为 Dapper 只运行 ADO.NET 命令。

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

    【讨论】:

      【解决方案4】:

      考虑到您所有的表都在单个数据库中,我不同意这里的一些答案中建议的TransactionScope 解决方案。参考this答案。

      1. TransactionScope一般用于分布式事务;跨越不同数据库的事务可能在不同的系统上。这需要在操作系统和 SQL Server 上进行一些配置,否则将无法正常工作。如果您的所有查询都针对单个数据库实例,则不建议这样做。
        但是,对于单个数据库,当您需要将代码包含在不受您控制的事务中时,这可能很有用。单库也不需要特殊配置。

      2. connection.BeginTransaction 是针对单个数据库实现事务(在 C#、VB.NET 等中)的 ADO.NET 语法。这不适用于多个数据库。

      所以,connection.BeginTransaction() 是更好的选择。

      处理事务的更好方法是按照this 答案中的说明实施 UnitOfWork。

      【讨论】:

      • 不需要多个数据库即可从 TransactionScope 中受益。特别有用的是它是环境的。它非常适合在事务中包装您不拥有或无法修改的代码。例如,当单元/集成测试代码在您想要回滚的地方进行数据库调用时,它可以发挥很大的作用。只需浮动一个 TransactionScope,测试代码,然后在测试清理期间进行处理。
      • @LarrySmith:同意;但问题与此无关。 OP 只是说他想在一个事务中插入多个表。包括接受的答案在内的一些答案建议使用TransactionScope,这对于OP想要的东西效率低下。我同意TransactionScope 在很多情况下是个好工具;但不是这个。
      【解决方案5】:

      在 Dapper 中进行交易有 3 种方法。

      1. 简单交易
      2. 来自事务范围的事务
      3. 使用 Dapper Transaction(additional nuget package和最受青睐的方法

      您可以从官方教程网站here了解更多关于这些交易方式的信息

      这里是交易方法的细分供参考

      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 交易

      这是在代码中实现事务的最有利的方法,因为它使代码易于阅读和实现。 SQL Transaction 的扩展实现称为 Dapper Transaction(您可以找到 here),它允许您直接运行 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();
          }
      }
      

      【讨论】:

      • 这是正确答案。我很惊讶我不得不找到最后一个答案。其他答案是不要使用 Dapper,或者不要使用 Tranaasctions。这解释了交易如何应该与 Dapper 一起工作 - 以及 Dapper 如何打算我们使用交易。奖励:现在我看到了,在 IDbTransaction 上添加 Execute 扩展方法本身就是 Dapper 的天才。
      • 每当我需要一个子方法来执行数据库工作时,我总是通过IDbConnectionIDbTransaction。通常你只会传递IDbConnection,但如果你也在一个事务中,你将不得不同时传递IDbTransaction。直到刚才我才意识到IDbTransaction 包含它来自的IDbConnection。所以现在我了解这位 25 年前的 Microsoft 开发人员在设计 ADO.net 接口时的想法 - 只需传递 IDbTransaction
      • @IanBoyd 我很高兴答案能帮上忙?我想我参加聚会有点晚了,但我想我会分享一些我对使用 Dapper 的理解和学习最近
      • 这是最好的答案。我不知道为什么没有更多的赞成票。
      【解决方案6】:

      丹尼尔的回答对我来说是预期的。为了完整起见,这里有一个 sn-p,它使用事务范围和 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 
      

      【讨论】:

      • @usr 取决于个人喜好。我更愿意知道第一次出现问题的时间,并且不会将日志语句视为垃圾。此外,我的回答仍然通过展示一种使用 dapper 进行交易的方式来宣传价值
      • @CodeNaked,首先,你的顺序错了。如果出现异常,将首先命中 catch 块,然后终止使用范围。其次,看看这个答案和引用的 MSDN 文档:stackoverflow.com/a/5306896/190476 第二次调用 dispose 是无害的,设计良好的对象会忽略第二次调用。否决票是不合理的!
      • @dotnetguy - 我并没有尝试传达第一个或第二个调用哪个Dispose 方法,只是它被调用了两次。至于“第二次调用 dispose 没有害处”这一点,这是一个很大的假设。我了解到文档和实际实现通常不一致。但如果你想要微软的话:msdn.microsoft.com/en-us/library/…
      • 那么,代码分析警告是您投反对票的理由吗?这不会使答案错误或误导 - 那是适当的反对意见。您为什么不编辑答案并在保留功能的同时提出更好的解决方案?堆栈溢出就是帮助和建设性的批评。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-10-24
      • 2021-11-19
      • 2016-10-29
      • 2021-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多