TransactionScope:避免分布式事务

时间:2010-07-06 15:22:01

标签: c# transactions transactionscope distributed-transactions

我有一个父对象(DAL的一部分),其中包含子对象的集合(List<t>)。

当我将对象保存回DB时,我输入/更新父对象,然后循环遍历每个子对象。为了可维护性,我将孩子的所有代码都放在一个单独的私有方法中。

我打算使用标准的ADO事务,但在我的旅行中,我偶然发现了TransactionScope对象,我相信这将使我能够在父方法中包含所有数据库交互(以及子方法中的所有交互)。一笔交易。

到目前为止一直很好......?

接下来的问题是如何在TransactionScope中创建和使用连接。我听说使用多个连接,即使它们是相同的DB也可以强制TransactionScope认为它是一个分布式事务(涉及一些昂贵的DTC工作)。

是这样吗?或者,正如我在其他地方读到的那样,使用相同的连接字符串(它将自己用于连接池)的情况会没问题吗?

更实际的是,我是......

  1. 在父&amp;中创建单独的连接;孩子(虽然有相同的连接字符串)
  2. 在父级中创建一个连接,将其作为参数传递(对我来说似乎很笨)
  3. 做点什么......?
  4. 更新

    虽然看起来我可以使用我常用的.NET3.5 +和SQL Server 2008+,但是这个项目的另一部分将使用Oracle(10g),所以我不妨练习一种可以使用的技术在项目中使用一致。

    所以我只需将连接传递给子方法。


    选项1代码示例:

    using (TransactionScope ts = new TransactionScope())
                {
                    using (SqlConnection conn = new SqlConnection(connString))
                    {
                        using (SqlCommand cmd = new SqlCommand())
                        {
                            cmd.Connection = conn;
                            cmd.Connection.Open();
                            cmd.CommandType = CommandType.StoredProcedure;
    
                            try
                            {
                                //create & add parameters to command
    
                                //save parent object to DB
                                cmd.ExecuteNonQuery();
    
                                if ((int)cmd.Parameters["@Result"].Value != 0)
                                {
                                    //not ok
                                    //rollback transaction
                                    ts.Dispose();
                                    return false;
                                }
                                else //enquiry saved OK
                                {
                                    if (update)
                                    {
                                        enquiryID = (int)cmd.Parameters["@EnquiryID"].Value;
                                    }
    
                                    //Save Vehicles (child objects)
                                    if (SaveVehiclesToEPE())
                                    {
                                        ts.Complete();
                                        return true;
                                    }
                                    else
                                    {
                                        ts.Dispose();
                                        return false;
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                //log error
                                ts.Dispose();
                                throw;
                            }
                        }
                    }
                }
    

3 个答案:

答案 0 :(得分:24)

当您使用TransactionScope跨多个连接进行交易时,许多数据库ADO提供程序(例如Oracle ODP.NET)确实会开始分布式事务 - 即使它们共享相同的连接字符串也是如此。

某些提供程序(如.NET 3.5+中的SQL2008)识别何时在引用相同连接字符串的事务范围中创建新连接,并且不会导致DTC工作。但是连接字符串中的任何差异(例如调整参数)都可能阻止这种情况发生 - 并且行为将恢复为使用分布式事务。

不幸的是,确保您的事务在不创建分布式事务的情况下协同工作的唯一可靠方法是将连接对象(或IDbTransaction)传递给需要在同一事务上“继续”的方法。 / p>

有时它有助于提升与您正在进行工作的类的成员的连接,但这可能会造成尴尬的情况 - 并且使控制连接对象的生命周期和处置变得复杂(因为它通常会妨碍使用using声明。)

答案 1 :(得分:2)

根据经验,我已确定(对于SQL Server提供程序)如果该进程可以利用连接池来共享父进程和子进程之间的连接(和事务),DTC不一定会参与其中。

这是一个很大的“if”,但是,根据您的示例,父进程创建的连接不能由子进程共享(在调用子进程之前不要关闭/释放连接)。这将导致跨越两个实际连接的事务,这将导致事务被提升为分布式事务。

似乎很容易重构代码以避免这种情况:在调用子进程之前,只需关闭父进程创建的连接。

答案 2 :(得分:1)

在您的示例中,TransactionScope仍然在方法的上下文中,您可以简单地在其下创建一个带有多个命令的SqlTransaction。如果要将事务移出方法(例如,该方法的调用方,或者访问多个数据库),请使用TransactionScope。

更新:别介意我刚看到孩子的电话。在这种情况下,您可以将连接对象传递给子类。此外,您不需要手动处理TransactionScope - 使用块就像try-finally块一样,即使在例外情况下也会执行dispose。

更新2:更好,将IDbTransaction传递给子类。可以从中检索连接。