20

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

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

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

到现在为止还挺好..?

所以下一个问题是如何在这个 TransactionScope 内创建和使用连接。我听说使用多个连接,即使它们连接到同一个数据库也会迫使 TransactionScope 认为它是一个分布式事务(涉及一些昂贵的 DTC 工作)。

是这样吗?或者,正如我似乎在其他地方读到的那样,使用相同的连接字符串(这将有助于连接池)的情况会很好吗?

更实际地说,我是否...

  1. 在父级和子级中创建单独的连接(尽管使用相同的连接字符串)
  2. 在父级中创建一个连接并将其作为参数传递(对我来说似乎很笨拙)
  3. 做点别的……?

更新:

虽然看起来我可以使用我常用的 .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;
                        }
                    }
                }
            }
4

3 回答 3

25

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

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

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

有时它有助于将连接提升到您正在执行工作的类的成员,但这会产生尴尬的情况 - 并使控制连接对象的生命周期和处置变得复杂(因为它通常会排除使用该using语句) .

于 2010-07-06T15:35:45.180 回答
3

根据经验,我已经确定(对于 SQL Server 提供程序)如果进程可以利用连接池在父进程和子进程之间共享连接(和事务),则不一定会涉及 DTC。

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

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

于 2010-07-06T15:47:44.660 回答
1

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

更新:没关系,我刚刚发现了孩子的电话。在这种情况下,您可以将连接对象传递给子类。此外,您不需要手动处置 TransactionScope - 使用块就像 try-finally 块一样,即使出现异常也会执行处置。

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

于 2010-07-06T15:30:38.497 回答