TransactionScope 导致阻塞?

发布于 2024-09-26 06:09:05 字数 478 浏览 8 评论 0原文

我正在针对数据库编写一些单元测试,并且我们正在使用事务来确保我们的测试数据最终被删除。

我遇到了一个问题,我正在测试的方法正在使用它们自己的 TransactionScope 对象,并且在访问数据库时似乎会阻塞。

这是在我的测试的基类中:

BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });

然后在我正在测试的方法中,它会:

using (TransactionScope scope = new TransactionScope())

第二个范围内的代码第一次接触数据库时,它会挂起。我有办法解决这个问题吗?

I'm writing some Unit tests against a database, and we're using transactions to make sure that our test data gets removed at the end.

I'm running into a problem where methods that I'm testing are using their own TransactionScope objects, and it seems to be blocking when hitting the database.

This is inside my test's base class:

BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });

and then inside the method I'm testing, it does:

using (TransactionScope scope = new TransactionScope())

The first time that the code inside the 2nd scope their touches the database, it hangs. Do I have any way around this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

纸短情长 2024-10-03 06:09:05

如果您使用的是数据库,那么您就没有进行单元测试,而您遇到的问题是真正的单元测试使用模拟和存根的原因之一。

现在你正在做的测试非常有价值,在某些情况下我实际上会做它们而不是单元测试。我将此标记为早期集成测试(EIT)。这里的关键点是,我们在使用真实的东西而不是单元测试模拟时发现了一类全新的错误。这里的关键是真实的东西。一旦你用人为的交易范围等伪造了环境,你就会失去 EIT 的大部分好处,因为你没有发现微妙的交互错误,或者(如你的情况)引入了人为的问题。

我会找到一种方法,用足够的测试数据快速填充数据库,并将其恢复到测试之外的状态。 “重置为已知状态”脚本对于此类测试非常有帮助。

If you are using a Database then you are not doing Unit testing, and the problems you are experiencing are one of the reason why true Unit testing uses Mocks and Stubs.

Now the tests you are doing are very valuable, and in some cases I would actually do them instead of Unit Testing. I label this Early Integration Testing (EIT). The key point here is that we find a whole new class of bugs when working with the real thing not the Unit Test mocks. And the key here is Real Thing. As soon as you fake up the environment with artifical transaction scopes etc. you are losing much of the benefit of EIT because you don't catch subtle interaction errors, or (as in your case) introduce artificial problems.

I would find a way to quickly populate the database with sufficient test data, and restore it to that state outside the test. A "reset to known state" script is very helpful for these kind of tests.

孤独陪着我 2024-10-03 06:09:05

当您嵌套 TransactionScope 实例时,您最终会得到一个分布式事务,而不是一个简单的本地事务。这种行为在所使用的数据库之间有所不同。例如,SQLServer 2008 不会升级到 DTX,除非实际涉及多个数据库。另一方面,Oracle 将始终升级为分布式事务,因为它不能不支持单个本地事务的共享连接。

根据您使用的数据库和 TransactionScopeOption,您可能会遇到死锁。发生这种情况是因为 DTX 通常需要表锁来确保它们可以原子提交。例如,在 Oracle 中,如果启动 DTX 并在完成之前崩溃或丢失连接,则可能会出现“可疑分布式事务”。这一“不确定”事务可能会锁定一个或多个表,以防止其他会话修改它们,直到 DBA 对挂起的事务 ID 执行 ROLLBACK FORCE 命令。某些数据库(如 SQLServer)尝试检测此类死锁并终止有问题的事务之一……但这肯定会发生。

我建议您选择以下两个选项之一:

  1. 确定您是否确实需要编写访问数据库的测试。通常,您可以使用模拟或存根来避免编写更改和更改的测试。然后回滚数据库。避免此类问题是有意义的,因为它既可以加快测试速度,又可以消除测试的潜在依赖性。但是,有时您无法这样做。
  2. 如果您确实需要针对数据库测试逻辑,请考虑修改代码,以便所有方法都使用相同的数据库连接来执行其 SQL。这将消除分布式事务的创建,并有望解决您的问题。

您可能还想查看数据库的待处理事务视图(在 Oracle 中,它称为 PENDING_TRANS$ ...在 SQLServer 中有 XACT_STATE() 函数)。

When you nest TransactionScope instances you can end up with a distributed transaction, rather than a simple local transaction. This behavior varies somewhat between database being used. SQLServer 2008, for instance doesn't escalate to a DTX unless multiple databases are actually involved. Oracle, on the other hand, will always escalate to a distributed transaction since it can't does not support sharing connections for a single local transaction.

Depending on which database and what TransactionScopeOption you are using, you may end up with a deadlock. This occurs because DTXs often require table locks to ensure that they can be committed atomically. In Oracle, for example, if you start a DTX and crash or lose your connection before you complete it, you can end up with an "In Doubt Distributed Transaction". This "In Doubt" transaction may lock one or more tables preventing other session from modifying them until a DBA performs a ROLLBACK FORCE command on the pending transaction ID. Some databases (like SQLServer) attempt to detect such deadlocks and terminate one of the offending transactions ... but this is guaranteed to happen.

I would suggest one of two options for you:

  1. Decide if you really need to write tests that hit the database. Often times, you can use a mock or stub to avoid the need to write tests that alter and then roll-back the database. Avoiding such problems makes sense since it both speeds up your tests and eliminates a potential dependency from them. However, sometimes you can't do this.
  2. If you really need to test your logic against the database, consider modifying your code so that all methods use the same database connection to execute their SQL. This will eliminate the creation of a distributed transaction, and hopefully overcome your issue.

You may also want to look into your database's pending transaction view (in Oracle it's called PENDING_TRANS$ ... in SQLServer there's the XACT_STATE() function).

那支青花 2024-10-03 06:09:05

您必须提交基本事务才能解锁您的测试方法,我认为这不是您想要的排序行为。您需要让测试方法的事务加入在基类中创建的外部“环境”(伞/父/基/外部)事务,而不是尝试创建自己的事务。

来自 MSDN CommittableTransactionClass 备注(强调我的):

建议您创建
隐式交易使用
TransactionScope 类,以便
环境事务上下文是
自动为您管理。
还应该使用 TransactionScope
和 DependentTransaction 类
需要使用的应用程序
同一交易跨多个
函数调用或多线程
电话。
有关此的更多信息
模型,请参阅实现
使用事务的隐式事务
范围
主题。

创建一个 CommittableTransaction 会
不自动设置环境
交易,也就是交易
你的代码执行。你可以得到或
通过调用设置环境事务
的静态 Current 属性
全局事务对象。了解更多
有关环境交易的信息,
请参阅“管理交易流程
使用 TransactionScopeOption”部分
隐式实现的
使用交易范围进行交易
话题。如果环境事务是
未设置,对资源的任何操作
经理不属于其中
交易。你需要明确地
设置和重置环境事务,
以确保资源管理器
在正确的交易下运作
上下文。

在提交 CommittableTransaction 之前,事务涉及的所有资源仍处于锁定状态。

正如 djna 指出的那样,使用事务来回滚测试期间所做的更改是相当滥用的。您的测试应该是一个好公民,并在finally子句中撤消和更改它对数据库的影响,以便在它之后运行的其他测试永远不会受到影响。如果您有很多表现不佳的测试,那么您现在可能不会走这条路。在这种情况下,请将您的基础更改为使用范围设置为 RequiresNew 的隐式事务,并在您的测试方法中使用 Required

You'll have to commit your base transaction in order to unblock your test method, which I assume isn't the sort behavior you desire. You need to get your test method's transaction to join the outer "ambient" (umbrella/parent/base/outer) transaction created in your base class, rather than try to create its own.

From MSDN CommittableTransactionClass Remarks (emphasis mine):

It is recommended that you create
implicit transactions using the
TransactionScope class, so that the
ambient transaction context is
automatically managed for you. You
should also use the TransactionScope
and DependentTransaction class for
applications that require the use of
the same transaction across multiple
function calls or multiple thread
calls.
For more information on this
model, see the Implementing An
Implicit Transaction Using Transaction
Scope
topic.

Creating a CommittableTransaction does
not automatically set the ambient
transaction, which is the transaction
your code executes in. You can get or
set the ambient transaction by calling
the static Current property of the
global Transaction object. For more
information on ambient transactions,
see the " Managing Transaction Flow
using TransactionScopeOption" section
of the Implementing An Implicit
Transaction Using Transaction Scope
topic. If the ambient transaction is
not set, any operation on a resource
manager is not part of that
transaction. You need to explicitly
set and reset the ambient transaction,
to ensure that resource managers
operate under the right transaction
context.

Until a CommittableTransaction has been committed, all the resources involved with the transaction are still locked.

As djna pointed out, using transactions to rollback changes made during testing is rather abusive. You test should be a good citizen and undo and changes it makes itself to the database in a finally clause so that other tests that may run after it never are affected. If you have lots of tests that aren't well behaved already, then you're probably not going to go this route now. In that case, change your base to use implicit transactions with a scope set to RequiresNew, and in your test method, use Required.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文