Sql server 重构ADO.NET-SqlTransaction与TransactionScope

Sql server 重构ADO.NET-SqlTransaction与TransactionScope,sql-server,ado.net,transactions,transactionscope,Sql Server,Ado.net,Transactions,Transactionscope,我“继承”了一个小小的C#方法,它创建了一个ADO.NET SqlCommand对象,并在要保存到数据库的项目列表上循环(SQL Server 2005) 现在,使用传统的SqlConnection/SqlCommand方法,为了确保一切正常,这两个步骤(删除旧条目,然后插入新条目)被包装到ADO.NET SqlTransaction中 using (SqlConnection _con = new SqlConnection(_connectionString)) { using (Sq

我“继承”了一个小小的C#方法,它创建了一个ADO.NET SqlCommand对象,并在要保存到数据库的项目列表上循环(SQL Server 2005)

现在,使用传统的SqlConnection/SqlCommand方法,为了确保一切正常,这两个步骤(删除旧条目,然后插入新条目)被包装到ADO.NET SqlTransaction中

using (SqlConnection _con = new SqlConnection(_connectionString))
{
   using (SqlTransaction _tran = _con.BeginTransaction())
   {
      try
      {
         SqlCommand _deleteOld = new SqlCommand(......., _con);
         _deleteOld.Transaction = _tran;
         _deleteOld.Parameters.AddWithValue("@ID", 5);

         _con.Open();

         _deleteOld.ExecuteNonQuery();

         SqlCommand _insertCmd = new SqlCommand(......, _con);
         _insertCmd.Transaction = _tran;

         // add parameters to _insertCmd

         foreach (Item item in listOfItem)
         {
            _insertCmd.ExecuteNonQuery();
         }

         _tran.Commit();
         _con.Close();
       }
       catch (Exception ex)
       {
          // log exception
          _tran.Rollback();
          throw;
       }
    }
}
现在,我最近读了很多关于.NET TransactionScope类的文章,我想知道,这里的首选方法是什么?我是否会通过切换到使用来获得任何东西(可读性、速度、可靠性)

using (TransactionScope _scope = new TransactionScope())
{
  using (SqlConnection _con = new SqlConnection(_connectionString))
  {
    ....
  }

  _scope.Complete();
}
你喜欢什么,为什么


Marc

Microsoft建议使用事务范围:

基本思想是,事务范围将为您管理“环境事务上下文”。您首先与一个数据库对话,您有一个sql事务,然后与第2个数据库对话,事务被提升为分布式事务

using (SqlConnection _con = new SqlConnection(_connectionString))
{
   using (SqlTransaction _tran = _con.BeginTransaction())
   {
      try
      {
         SqlCommand _deleteOld = new SqlCommand(......., _con);
         _deleteOld.Transaction = _tran;
         _deleteOld.Parameters.AddWithValue("@ID", 5);

         _con.Open();

         _deleteOld.ExecuteNonQuery();

         SqlCommand _insertCmd = new SqlCommand(......, _con);
         _insertCmd.Transaction = _tran;

         // add parameters to _insertCmd

         foreach (Item item in listOfItem)
         {
            _insertCmd.ExecuteNonQuery();
         }

         _tran.Commit();
         _con.Close();
       }
       catch (Exception ex)
       {
          // log exception
          _tran.Rollback();
          throw;
       }
    }
}
事务范围确实适合您,因此您可以专注于系统的功能,而不是管道

编辑


当您使用事务范围时,该范围内的所有内容都将被事务覆盖。因此,保存一行代码,将命令连接到事务。这是一个可能的错误源,例如,如果有1000分之一的机会这一行被忽略,你会错过多少

编辑2


同意下面关于Triynko的评论。然而,我们使用实体框架,EF将自动关闭并重新打开连接,以便在事务中登记它。它并没有物理上关闭连接,更像是将其释放到连接池并获得一个新的连接,它可以是相同的连接,也可以是不同的连接

将现有代码切换到使用
TransactionScope
不会立即获得任何好处。由于它提供的灵活性,您应该在将来的开发中使用它。这将使将来在事务中包含ADO.NET调用以外的内容变得更容易

using (SqlConnection _con = new SqlConnection(_connectionString))
{
   using (SqlTransaction _tran = _con.BeginTransaction())
   {
      try
      {
         SqlCommand _deleteOld = new SqlCommand(......., _con);
         _deleteOld.Transaction = _tran;
         _deleteOld.Parameters.AddWithValue("@ID", 5);

         _con.Open();

         _deleteOld.ExecuteNonQuery();

         SqlCommand _insertCmd = new SqlCommand(......, _con);
         _insertCmd.Transaction = _tran;

         // add parameters to _insertCmd

         foreach (Item item in listOfItem)
         {
            _insertCmd.ExecuteNonQuery();
         }

         _tran.Commit();
         _con.Close();
       }
       catch (Exception ex)
       {
          // log exception
          _tran.Rollback();
          throw;
       }
    }
}

顺便说一句,在您发布的示例中,
SqlCommand
实例应该在
中使用
块。

请注意,使用事务范围有时我们会遇到很多问题,因为我们必须在服务器中执行许多设置,例如设置DTC,防火墙等。所以我建议使用SqlTransaction更节省实施成本。

我更喜欢TransactionScope。它并不是在所有场景中都能完美工作,但在您描述的场景中,它是更好的解决方案

我的理由是:

  • 事务中的登记是自动的
  • 异常事件中的事务回滚是自动的
  • 总之,结果是代码少了一点,总体上设计更稳健,因为系统正在为我处理一些细节;这是我必须记住做的少一件事


    此外,当DAL中有许多嵌套方法时,透明事务注册尤其有用——尽管您必须注意不要意外地将事务转换为需要DTC的分布式事务,如果使用多个SqlConnections,可能会发生这种情况,即使它们指向相同的分贝。

    好吧,也许现在就这样太晚了。。。但无论如何,我会写下来给那些感兴趣的人


    因为我现在有了一个更好的图景,在我当前基于
    SqlTransaction
    的方法遇到了很多困难之后,我可能会改为使用
    TransactionScope
    ,正如我所见。。。
    TransactionScope
    的主要优点是它可以很容易地在业务层使用

    也很晚了。。。即使数据库不支持嵌套事务,您也可以轻松地在业务层中拥有“嵌套”事务。NET控制嵌套并最终使用一个数据库事务(至少在SQL Server 2008+的情况下)。这使得在原始意图之外重用数据访问代码,作为更大事务的一部分变得更加容易。

    OK,。谢谢你。但是,如果我重构一切(不仅仅是这一个示例),使用TransactionScope()而不是ADO.NET嵌入式事务,我会从中受益吗?只要问一下付出的努力是否值得——我从中获得了什么?“当您使用事务范围时,该范围内的所有内容都会被事务覆盖。”不,所有内容都不会被覆盖。只有在作用域中登记的连接上发出的命令才受作用域的影响。如果在作用域中打开连接,则会自动在作用域中登记连接,否则,在通过调用SqlConnection.EnclestTransaction创建作用域后,需要在作用域中手动登记已打开的连接。例如,如果打开连接,则创建事务范围。。。您的任何命令都不会参与该事务。@Triynko:您对相应MSDN文章的评论非常好!好的,谢谢John-是的,你是对的-这个例子在using()块中没有SqlCommand(但是!)-这是正在进行的工作:-)re:SqlCommand在using中,除了垃圾收集之外还有其他原因吗,例如:SQLConnection的Dispose()调用Close()方法,Dispose()也是如此调用任何SQLCommand方法?如果一个类实现了
    IDisposable
    ,并且您创建了这个类的实例,那么您应该对它调用Dispose。最简单的方法是使用
    块。我发现最好养成一个习惯,总是执行一个。
    
    using (SqlConnection _con = new SqlConnection(_connectionString))
    {
       using (SqlTransaction _tran = _con.BeginTransaction())
       {
          try
          {
             SqlCommand _deleteOld = new SqlCommand(......., _con);
             _deleteOld.Transaction = _tran;
             _deleteOld.Parameters.AddWithValue("@ID", 5);
    
             _con.Open();
    
             _deleteOld.ExecuteNonQuery();
    
             SqlCommand _insertCmd = new SqlCommand(......, _con);
             _insertCmd.Transaction = _tran;
    
             // add parameters to _insertCmd
    
             foreach (Item item in listOfItem)
             {
                _insertCmd.ExecuteNonQuery();
             }
    
             _tran.Commit();
             _con.Close();
           }
           catch (Exception ex)
           {
              // log exception
              _tran.Rollback();
              throw;
           }
        }
    }