TransactionScope 在用于数据库单元测试时导致 TransactionManagerCommunicationException
我想在数据库中运行一些存储过程测试,而不会实际影响数据(或者,更准确地说,在测试运行后不会产生持久影响)。
经过一番研究,我想出了在 Visual Studio 2010 测试项目中使用 TransactionScope 的方法,例如
using( new TransactionScope())
{
using( SqlConnection connection = new SqlConnection("someConnectionString"))
{
connection.Open();
using( SqlCommand command = new SqlCommand( "some sql", connection ))
{
// Do some database stuff...
}
}
}
现在,只要我将所有这些都放在一个测试方法中,即自动回滚对数据库的所有更改,该方法就可以正常工作当 TransactionScope 的 using 块完成时。
我的问题是现在我想在 ClassInitialize 中执行一些数据库操作,因此我只需为每个测试类执行一次,而不是为每个测试方法执行一次。当我创建一个公共 TransactionScope 属性并在 ClassInitialize 方法中为其分配一个 TransactionScope 实例时,这可以正常工作。一旦我在一个测试方法中执行任何与数据库相关的操作,我就会在该方法中遇到 TransactionManagerCommunicationException。
我不太明白为什么会出现这种情况,而且我还想知道我的方法是否有错误,或者如何让它工作而无需设置 TransactionScope(包括所有设置的内容)再次在每个测试方法中进行测试。
编辑
下面的代码摘录,我希望这能提供足够的信息:
public TransactionScope Scope { get; set; }
[ClassInitialize]
public static void ClassInitialize( TestContext testContext )
{
Scope = new TransactionScope();
// Do some db set up stuff, e.g. create records used for tests etc.
}
[ClassCleanup]
public static void ClassCleanup()
{
Scope.Dispose();
}
[TestMethod]
public void MyTestMethod()
{
using( SqlConnection connection = new SqlConnection( "someConnectionString" ) )
{
DataTable result = new DataTable();
using( SqlCommand command = new SqlCommand( "spName", connection ) )
{
command.CommandType = CommandType.StoredProcedure;
using( SqlDataAdapter adapter = new SqlDataAdapter() )
{
adapter.SelecteCommand = command;
// The next line causes the exception to be thrown
adapter.Fill( result );
}
}
// Assertions against DataTable result
}
}
例外是
TransactionManagerCommunicationException 未被用户代码处理 分布式事务管理器 (MSDTC) 的网络访问已被禁用。请使用组件服务管理工具在 MSDTC 的安全配置中启用 DTC 进行网络访问。
我知道我可以尝试更改设置,但我不明白为什么我一开始就遇到异常 - 与在单个(测试)方法中使用上面的代码相比有什么不同?
预先感谢并
致以最诚挚的问候
G.
I'd like to run some tests of stored procedures in my database without actually affecting the data (or, to put it more exactly, without a lasting impact after the test has run).
After some research I came up with the approach of using TransactionScope within my Visual Studio 2010 test project such as
using( new TransactionScope())
{
using( SqlConnection connection = new SqlConnection("someConnectionString"))
{
connection.Open();
using( SqlCommand command = new SqlCommand( "some sql", connection ))
{
// Do some database stuff...
}
}
}
Now this works fine as long as I put all of this within a single test method, i.e. all my changes to the database are automatically rolled back when the using block for TransactionScope is finished.
My problem is now that I'd like to do some database stuff in ClassInitialize so I only have to do it once per test class and not for every test method. When I create a public TransactionScope property and assign it an instance of TransactionScope in the ClassInitialize method, this works okay. As soon as I do any database related stuff in one of my test methods, I run into a TransactionManagerCommunicationException within that method.
I don't quite understand why that is the case, and I'd also like to know whether there is a mistake in my approach or how I can get it to work without having to set up the TransactionScope including all set up stuff for the tests within each test method again.
EDIT
Code excerpt below, I hope this gives enough information:
public TransactionScope Scope { get; set; }
[ClassInitialize]
public static void ClassInitialize( TestContext testContext )
{
Scope = new TransactionScope();
// Do some db set up stuff, e.g. create records used for tests etc.
}
[ClassCleanup]
public static void ClassCleanup()
{
Scope.Dispose();
}
[TestMethod]
public void MyTestMethod()
{
using( SqlConnection connection = new SqlConnection( "someConnectionString" ) )
{
DataTable result = new DataTable();
using( SqlCommand command = new SqlCommand( "spName", connection ) )
{
command.CommandType = CommandType.StoredProcedure;
using( SqlDataAdapter adapter = new SqlDataAdapter() )
{
adapter.SelecteCommand = command;
// The next line causes the exception to be thrown
adapter.Fill( result );
}
}
// Assertions against DataTable result
}
}
The exception is
TransactionManagerCommunicationException was unhandled by user code
Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool.
I understand that I could try and change the settings, but I do not understand why I get the exception to begin with - what is different compared to having the code above in a single (test) method?
Thanks in advance and
best regards
G.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您的异常是说未启用 MSDTC。我的猜测是,当您单独使用 TransactionScope 时,它只是创建本地 SQL 事务——不需要 DTC。但是,当您通过多个连接共享 TransactionScope 时,事务会通过 DTC“提升”为分布式事务,而您可能尚未启用 DTC。
尝试在本地计算机和服务器上的 MSDTC 上启用网络访问。根据您的操作系统,执行此操作的步骤略有不同。 以下是在 Win 2003 Server 中执行此操作的方法。 这是 Win 2008 的链接。请注意,您可能还需要通过防火墙启用 DTC(在最后一个链接中进行了说明...)
Your exception is saying that MSDTC isn't enabled. My guess is that when you were using TransactionScope individually, it was just creating local SQL transactions -- which don't require DTC. However, when you share a TransactionScope over multiple connections, the transaction gets "promoted" to a distributed transaction through the DTC, which you may not have enabled.
Try enabling network access on MSDTC on your local machine and the server. The steps for doing so vary a little depending on your OS. Here's how to do it in Win 2003 Server. Here's a link for Win 2008. Note that you will likely need to enable DTC through your firewalls as well (explained in the last link...)
您可以创建这样的设置:
至于您遇到的错误,您能否准确发布您如何使用您的实现?
You could create your setup something like this:
As for the error you're getting, could you post exactly how you're using your implementation?
我成功使用的一种方法是创建一个实现设置和拆卸的基类。在设置方法中,您创建一个新的事务范围并将其存储在私有类变量中。在拆卸方法中,您回滚事务范围。
我使用的是NUIt,但MSTest的原理应该是相同的。这里的线索是,
SetUp
和TearDown
在每个单元测试之前和之后执行一次,以确保单元测试之间的隔离。此外,正如 @blech 提到的,Microsoft 分布式事务协调器 (MSDTC) 服务必须运行才能使该解决方案发挥作用。
One approach I have used with success is creating a base class that implements setup and teardown. Within the setup method you create a new transaction scope and store it in a private class variable. Within the teardown method you rollback the transaction scope.
I'm using NUNit, but the principle should be the same for MSTest. The clue here is that
SetUp
andTearDown
executes once before and after each unit test to ensure isolation among unit tests.Also, as @blech mentions, the Microsoft Distributed Transaction Coordinator (MSDTC) service must be running for this solution to work.