TransactionScope:避免分布式事务
我有一个父对象(DAL 的一部分),其中包含子对象的集合 (List
)。
当我将对象保存回数据库时,我输入/更新父对象,然后循环遍历每个子对象。为了可维护性,我将子级的所有代码放入单独的私有方法中。
我原本打算使用标准 ADO 事务,但在旅途中,我偶然发现了 TransactionScope 对象,我相信它将使我能够将父方法中的所有数据库交互(以及子方法中的所有交互)包装在一个事务中。
到目前为止,一切都很好..?
因此,下一个问题是如何在这个 TransactionScope 内创建和使用连接。我听说使用多个连接,即使它们连接到同一个数据库,也会迫使 TransactionScope 认为它是分布式事务(涉及一些昂贵的 DTC 工作)。
是这样吗?或者,正如我似乎在其他地方读到的那样,使用相同的连接字符串(这将有助于连接池)就可以了?
更实际地说,我是否...
- 在父级和父级中创建单独的连接?子级(尽管具有相同的连接字符串)
- 在父级中创建一个连接并将其作为参数传递(对我来说似乎很笨拙)
- 做其他事情...?
更新:
虽然看起来我可以使用常用的 .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;
}
}
}
}
I have a parent object (part of a DAL) that contains, amongst other things, a collection (List<t>
) of child objects.
When I'm saving the object back to the DB, I enter/update the parent, and then loop through each child. For maintainability, I've put all the code for the child into a separate private method.
I was going to use standard ADO Transactions, but on my travels, I stumbled across the TransactionScope object, which I believe will enable me to wrap all DB interaction in the parent method (along with all interaction in the child method) in one transaction.
So far so good..?
So the next question is how to create and use connections within this TransactionScope. I have heard that using multiple connections, even if they are to the same DB can force TransactionScope into thinking that it is a distributed transaction (involving some expensive DTC work).
Is the case? Or is it, as I seem to be reading elsewhere, a case that using the same connection string (which will lend itself to connection pooling) will be fine?
More practically speaking, do I...
- Create separate connections in the parent & child (albeit with the same connection string)
- Create a connection in the parent an pass it through as a parameter (seems clumsy to me)
- Do something else...?
UPDATE:
While it appears I would be OK using my usual .NET3.5+ and SQL Server 2008+, another part of this project will be using Oracle (10g) so I might as well practice a technique that can be used consistently across projects.
So I'll simply pass the connection through to the child methods.
Option 1 Code Sample:
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;
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
当您使用
TransactionScope
跨多个连接进行事务处理时,许多数据库 ADO 提供程序(例如 Oracle ODP.NET)确实会开始分布式事务 - 即使它们共享相同的连接字符串。某些提供程序(例如 .NET 3.5+ 中的 SQL2008)会识别何时在引用相同连接字符串的事务范围中创建新连接,并且不会导致 DTC 工作。但是连接字符串中的任何变化(例如调整参数)都可能会阻止这种情况的发生 - 并且该行为将恢复为使用分布式事务。
不幸的是,确保事务在不创建分布式事务的情况下协同工作的唯一可靠方法是将连接对象(或 IDbTransaction)传递给需要在同一事务上“继续”的方法。
有时,它有助于将连接提升到您正在执行工作的类的成员,但这可能会造成尴尬的情况 - 并且使控制连接对象的生命周期和处置变得复杂(因为它通常会阻止使用
using
语句)。Many database ADO providers (such as Oracle ODP.NET) do indeed begin distributed transactions when you use
TransactionScope
to transact across multiple connections - even when they share the same connection string.Some providers, (like SQL2008 in .NET 3.5+) recognizes when a new connection is created in a transaction scope that refers to the same connection string, and will not result in DTC work. But any variance in the connection string (such as tuning parameters) may preclude this from occuring - and the behavior will revert to using a distributed transaction.
Unfortunately, the only reliable means of ensuring your transactions will work together without creating a distributed transaction is to pass the connection object (or the
IDbTransaction
) to methods that need to "continue" on the same transaction.Sometimes it helps to elevate the connection to a member of the class in which you're doing the work, but this can create awkward situations - and complicates controlling the lifetime and disposal of the connection object (since it generally precludes use of the
using
statement).根据经验,我已经确定(对于 SQL Server 提供程序)如果进程可以利用连接池在父进程和子进程之间共享连接(和事务),DTC 不一定会发生参与其中。
这是一个很大的“如果”,但是,根据您的示例,子进程无法共享父进程创建的连接(在调用子进程之前不要关闭/释放连接)。这将导致一个事务跨越两个实际连接,从而导致该事务被提升为分布式事务。
似乎很容易重构代码来避免这种情况:只需在调用子进程之前关闭父进程创建的连接即可。
Empirically, I have determined that (for the SQL Server provider) if the process can take advantage of connection pooling to share the connection (and the transaction) between the parent and child processes, the DTC will not necessarily become involved.
This is a big "if", however, as per your example, the connection created by the parent process cannot be shared by the child processes (you do not close/release the connection before invoking the child processes). This will result in a transaction that spans two actual connections, which will result in the transaction being promoted to a distributed transaction.
It seems that it would be easy to refactor your code to avoid this scenario: just close the connection created by the parent process before invoking the child processes.
在您的示例中,TransactionScope 仍然位于方法的上下文中,您可以简单地创建一个 SqlTransaction,并在其下面包含多个命令。如果您想要将事务移出方法(即该方法的调用者),或者如果您访问多个数据库,请使用 TransactionScope。
更新:没关系,我刚刚发现了孩子的电话。在这种情况下,您可以将连接对象传递给子类。此外,您不需要手动处置 TransactionScope - 使用块就像 try-finally 块一样,即使在出现异常时也会执行处置。
更新 2: 更好的是,将 IDbTransaction 传递给子类。可以从中检索连接。
In your example the TransactionScope is still in the context of a method, you could simply create a SqlTransaction with multiple commands beneath that. Use TransactionScope if you want to move the transaction out of a method, to say, the caller of that method, or if you access multiple databases.
Update: never mind I just spotted the child call. In this situation, you could pass the connection object to child classes. Also, you don't need to manually dispose the TransactionScope - using blocks act like try-finally blocks and will execute the dispose even on exceptions.
Update 2: better yet, pass the IDbTransaction to the child class. The connection can be retrieved from that.