TransactionScope打破SqlConnection池?

Con*_*zen 5 c# sql sql-server transactions

我对TransactionScope和异步/同步SQL调用有一种奇怪的情况,我很难理解。我希望对这些操作的来龙去脉有较深了解的人能对此问题有所启发。

情况: 我有一个NUnit测试夹具,该夹具在[SetUp]期间创建一个TransactionScope,并将其放在[TearDown]处,以使每个测试都在相同的数据上运行。我进行了一系列测试,这些测试开始了对数据库的异步操作,然后对数据库执行了同步操作。第一个这样的测试成功完成。第二次这样的测试失败,并显示“已经有与此命令关联的打开的DataReader,必须先关闭它”。

  1. 如果我完全注释掉TransactionScope,所有测试都会通过。
  2. 我尝试了各种不同的TransactionScope选项和Complete / Dispose,但是发生相同的问题。
  3. 我正在NUnit测试.NET 4.5.1上使用Resharper测试运行程序。
  4. 我意识到“正确”的答案可能是“使所有内容异步等待”。不幸的是,这不是我的选择。
  5. 我不想启用MARS,因为此问题仅在测试中发生。
  6. 由于潜在的死锁,我不想使用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();            
    }
}`
Run Code Online (Sandbox Code Playgroud)

小智 0

看看这个答案

我认为要点是,如果没有特殊的连接字符串属性,就不能同时在同一个连接上执行两个活动的 sql 命令。当您在事务范围下进行操作时,您应该会发现两个 SqlConnection 对象具有相同的客户端 ID。但是,如果您删除事务范围,它们就会不同,我认为这意味着它们在单独的连接上运行。

将“MultipleActiveResultSets=true”添加到连接字符串解决了我的问题。另一种选择是替换

DoAsyncThing().ConfigureAwait(false); 
Run Code Online (Sandbox Code Playgroud)

DoAsyncThing().ConfigureAwait(false).GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

这将在启动第二个命令之前终止第一个命令。