【发布时间】:2018-11-21 05:09:31
【问题描述】:
我对 TransactionScope 和异步/同步 SQL 调用有一个奇怪的情况,我很难理解。我希望对这些操作的来龙去脉有更深入了解的人可以对这个问题有所了解。
情况: 我有一个 NUnit testfixture,它在 [SetUp] 期间创建一个 TransactionScope 并在 [TearDown] 处处理它,以让每个测试在相同的数据上运行。我有一系列测试,它们在数据库上启动异步操作,然后在数据库上执行同步操作。第一次这样的测试成功完成。第二个这样的测试失败,“已经有一个打开的 DataReader 与此命令关联,必须先关闭。”。
- 如果我完全注释掉 TransactionScope,则所有测试都通过。
- 我尝试了各种不同的 TransactionScope 选项和 Complete / Dispose,但出现了同样的问题。
- 我在 NUnit 测试 .NET 4.5.1 上使用 Resharper 测试运行程序。
- 我意识到“正确”的答案可能是“让一切异步等待”。不幸的是,这不是我的选择。
- 我不想启用 MARS,因为此问题仅在测试中出现。
- 由于潜在的死锁,我不想使用 GetAwaiter().GetResult()。
在我看来,一旦调用了 TransactionScope.Dispose/Complete,自动 SQLConnection 池就会失去对打开 DataReaders 的连接的跟踪。它将相同的 SqlConnection 分发给两个同时运行的操作,然后第二个死掉。
我的主要问题是“是什么导致了这种行为(具体而言)?”
我的次要问题是“有什么办法可以安全地解决问题?”
下面的复制代码打印出客户端连接 ID。在我的机器上,第二个测试用例中 ASYNC 和 SYNC 调用的 ClientConnectionId 始终相同。
复制代码:
[TestFixture]
public class DataReaderTests
{
private TransactionScope _scope;
private string _connString = @"my connection string";
[SetUp]
public void Setup()
{
var options = new TransactionOptions()
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromMinutes(1)
};
_scope = new TransactionScope(TransactionScopeOption.RequiresNew, options, TransactionScopeAsyncFlowOption.Enabled);
}
[Test]
[TestCase("First")]
[TestCase("Second")]
public void Test(string name)
{
DoAsyncThing().ConfigureAwait(false);
using (var conn = new SqlConnection(_connString))
{
try
{
conn.Open();
Console.WriteLine("SYNC: " + conn.ClientConnectionId);
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT 1";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int id = reader.GetInt32(0);
}
}
}
}
catch (TransactionAbortedException tax)
{
Console.WriteLine("ERROR: " + ((SqlException)tax.InnerException.InnerException).ClientConnectionId);
throw;
}
}
}
private async Task DoAsyncThing()
{
using (var connection = new SqlConnection(_connString))
{
await connection.OpenAsync();
Console.WriteLine("ASYNC: " + connection.ClientConnectionId);
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = "WAITFOR DELAY '00:02';";
await cmd.ExecuteNonQueryAsync();
Console.WriteLine("ASYNC COMPLETE");
}
}
}
[TearDown]
public void Teardown()
{
_scope.Dispose();
}
}`
【问题讨论】:
-
您是否知道事务中登记的 SqlConnections 的特殊连接池行为,如下所述:docs.microsoft.com/en-us/dotnet/framework/data/adonet/…。这肯定是出乎意料的,并且可能不支持在单个事务中同时访问连接池。
标签: c# sql sql-server transactions