【发布时间】:2017-02-07 22:01:44
【问题描述】:
原始问题
对于针对数据库的集成测试,我一直在 NUnit SetUp 方法中设置 TransactionScope 并在 TearDown 中回滚。当我将测试切换为对所有内容使用异步时,更改并没有回滚。我将SetUp 从async Task 切换到void,它开始按预期运行。
源自案例的简短问题
将 TransactionScope 与 async/await 一起使用时,您是否需要在与 TransactionScope 相同的线程上创建 SqlConnection 才能传播到所有后续异步操作?
长问题
.NET 将 TransactionScopeAsyncFlowOption 添加到 TransactionScope 中,并将其描述为控制“与事务范围关联的环境事务是否会跨线程继续流动”
根据我看到的行为,您似乎仍然需要在 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);
}
}
}
【问题讨论】:
-
你能明确地将命令与适当的事务关联起来吗?
-
请记下您正在使用的 .NET 运行时。
-
TransactionScope 不影响异步操作。该选项是必需的,因为
await没有将TransactionScope保留到以前版本的同步上下文中。 测试 代码虽然使用Task.StartNew在不同的线程上打开连接。没有要保留的上下文。 为什么你这样做而不是使用SqlConnection.OpenAsync()? -
换句话说 - 您的代码明确丢弃 TransactionScope。没有可登记的环境 TransactionScope。
-
“与事务范围关联的环境事务是否会跨线程延续” - 是的,但是您的代码没有使用线程延续来链接创建
TransactionScope和更高版本的块using块 - 所以人们不会期望这个选项在这里有任何影响。换句话说,您在StartNewlambda 中创建了一个环境事务,但在周围的代码中从来没有环境事务。
标签: c# .net sql-server async-await