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