C# 使用异步/等待的TransactionScope登记

C# 使用异步/等待的TransactionScope登记,c#,.net,sql-server,async-await,C#,.net,Sql Server,Async Await,原版 对于访问数据库的集成测试,我已经在NUnitSetUp方法中设置了TransactionScope,并在TearDown中回滚。当我将测试切换为对所有内容使用异步时,更改并没有回滚。我将我的设置从异步任务切换到无效,它开始按预期运行 从案例中引出的简短问题 将TransactionScope与async/await一起使用时,是否需要在TransactionScope所在的同一线程上创建SqlConnection,以便传播到所有后续异步操作 长问题 .NET在TransactionScop

原版

对于访问数据库的集成测试,我已经在NUnit
SetUp
方法中设置了
TransactionScope
,并在
TearDown
中回滚。当我将测试切换为对所有内容使用异步时,更改并没有回滚。我将我的
设置
异步任务
切换到
无效
,它开始按预期运行

从案例中引出的简短问题

将TransactionScope与async/await一起使用时,是否需要在TransactionScope所在的同一线程上创建SqlConnection,以便传播到所有后续异步操作

长问题


.NET在TransactionScope中添加了TransactionScope AsyncFlowOption,并将其描述为控制“与事务作用域关联的环境事务是否将在线程延续之间流动”

根据我看到的行为,看起来您仍然需要在TransactionScope的根线程上实例化SqlConnections,否则命令不会自动登记到环境事务中。这是机械意义上的,我只是在文档中找不到它。所以我想我想知道是否有人对这个主题了解得更多一些

这是我在试图弄清楚我的具体问题时得出的测试用例(使用NUnit和Dapper),这是一个超时,表被一个事务锁定,我的第二个连接没有登记(我想?)

NUnit相关旁注:如果您正在测试异步代码,并且希望在TransactionScope中运行所有内容,请不要将[SetUp]方法设置为异步任务。如果您这样做,它可能会在与实际测试方法不同的线程上运行,并且您的连接不会被登记在事务中

public class SqlConnectionTimeout
{
    public string DatabaseName = "AsyncDeadlock_TestCase";
    public string ConnectionString = "";

    [Test, Explicit]
    public void _RecreateDatabase()
    {
        using (var connection = IntegrationTestDatabase.RecreateDatabase(DatabaseName))
        {
            connection.Execute(@"
                CREATE TABLE [dbo].[example](
                    [id] [int] IDENTITY(1,1) NOT NULL,
                    [number] [int] NOT NULL,
                    CONSTRAINT [PK_example] PRIMARY KEY CLUSTERED 
                    (
                        [id] ASC
                    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
                ) ON [PRIMARY];

                CREATE TABLE [dbo].[exampleTwo](
                    [id] [int] IDENTITY(1,1) NOT NULL,
                    [number] [int] NOT NULL,
                    CONSTRAINT [PK_exampleTwo] PRIMARY KEY CLUSTERED 
                    (
                        [id] ASC
                    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
                ) ON [PRIMARY];
            ");
        }
    }

    [Test]
    public async Task Timeout()
    {
        TransactionScope transaction = null;
        SqlConnection firstConnection = null;

        Task.Factory.StartNew(() =>
        {
            transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);

            firstConnection = new SqlConnection(ConnectionString);
            firstConnection.Open();
        }).Wait();

        using (transaction)
        {
            using (firstConnection)
            {
                using (var secondConnection = new SqlConnection(ConnectionString))
                {
                    await secondConnection.OpenAsync();

                    await firstConnection.ExecuteAsync("INSERT INTO example (number) VALUES (100);");

                    Assert.ThrowsAsync<SqlException>(async () => await secondConnection.QueryAsync<int>(
                        new CommandDefinition("SELECT * FROM example", commandTimeout: 1)
                    ));
                }
            }
        }
    }

    [Test]
    public async Task NoTimeout()
    {
        TransactionScope transaction = null;
        SqlConnection firstConnection = null;
        SqlConnection secondConnection = null;

        Task.Factory.StartNew(() =>
        {
            transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);

            firstConnection = new SqlConnection(ConnectionString);
            firstConnection.Open();

            secondConnection = new SqlConnection(ConnectionString);
            secondConnection.Open();
        }).Wait();

        using (transaction)
        {
            using (firstConnection)
            {
                using (secondConnection )
                {
                    await firstConnection.ExecuteAsync("INSERT INTO example (number) VALUES (100);");

                    await secondConnection.QueryAsync<int>(
                        new CommandDefinition("SELECT * FROM example", commandTimeout: 1)
                    );
                }
            }
        }

        // verify that my connections correctly enlisted in the transaction
        // and rolled back my insert

        using (var thirdConnection = new SqlConnection(ConnectionString))
        {
            thirdConnection.Open();

            var count = await thirdConnection.ExecuteScalarAsync("SELECT COUNT(*) FROM example");
            Assert.AreEqual(0, count);
        }
    }
}
公共类SqlConnectionTimeout
{
公共字符串DatabaseName=“AsyncDeadlock\u TestCase”;
公共字符串ConnectionString=“”;
[测试,明确]
public void\u重新创建数据库()
{
使用(var connection=IntegrationTestDatabase.RecreateDatabase(DatabaseName))
{
连接。执行(@“
创建表[dbo]。[示例](
[id][int]标识(1,1)不为空,
[number][int]不为空,
约束[PK_示例]主键群集
(
[id]ASC
)在[主]上打开(PAD\u INDEX=OFF,STATISTICS\u norecocomputer=OFF,IGNORE\u DUP\u KEY=OFF,ALLOW\u ROW\u LOCKS=ON,ALLOW\u PAGE\u LOCKS=ON)
)关于[初级];
创建表[dbo]。[示例二](
[id][int]标识(1,1)不为空,
[number][int]不为空,
约束[PK_示例二]主键群集
(
[id]ASC
)在[主]上打开(PAD\u INDEX=OFF,STATISTICS\u norecocomputer=OFF,IGNORE\u DUP\u KEY=OFF,ALLOW\u ROW\u LOCKS=ON,ALLOW\u PAGE\u LOCKS=ON)
)关于[初级];
");
}
}
[测试]
公共异步任务超时()
{
TransactionScope transaction=null;
SqlConnection firstConnection=null;
Task.Factory.StartNew(()=>
{
事务=新事务范围(TransactionScopeAsyncFlowOption.Enabled);
firstConnection=新的SqlConnection(ConnectionString);
firstConnection.Open();
}).Wait();
使用(事务)
{
使用(第一次连接)
{
使用(var secondConnection=newsqlconnection(ConnectionString))
{
等待secondConnection.OpenAsync();
等待firstConnection.ExecuteAsync(“插入示例(数字)值(100);”;
Assert.ThrowsAsync(async()=>await secondConnection.QueryAsync(
新命令定义(“从示例中选择*”,命令超时:1)
));
}
}
}
}
[测试]
公共异步任务NoTimeout()
{
TransactionScope transaction=null;
SqlConnection firstConnection=null;
SqlConnection-secondConnection=null;
Task.Factory.StartNew(()=>
{
事务=新事务范围(TransactionScopeAsyncFlowOption.Enabled);
firstConnection=新的SqlConnection(ConnectionString);
firstConnection.Open();
secondConnection=新的SqlConnection(ConnectionString);
secondConnection.Open();
}).Wait();
使用(事务)
{
使用(第一次连接)
{
使用(第二连接)
{
等待firstConnection.ExecuteAsync(“插入示例(数字)值(100);”;
等待secondConnection.QueryAsync(
新命令定义(“从示例中选择*”,命令超时:1)
);
}
}
}
//验证我的连接是否正确登记在事务中
//把我的插页卷回去
使用(var thirdConnection=newsqlconnection(ConnectionString))
{
第三个连接。Open();
var count=await thirdConnection.ExecuteScalarAsync(“从示例中选择count(*));
Assert.AreEqual(0,count);
}
}
}

根据对我的回答的评论,特别是Panagiotis Kanavos的回答:

transactionscope的异步选项使transactionscope通过同步上下文从一个任务传递到另一个任务。我问题中的测试代码在与TransactionScope不同的线程上打开了连接,而TransactionScope是在Task.StartNew上打开的,因此没有要传递的上下文,也没有要传递给propa的事务