Первого выпускTransactionScope прикрепления с асинхронным/ждет
Для интеграции тестов, поражающих базу данных, я настройка TransactionScope
в методе NUnit SetUp
и откат в TearDown
. Когда я переключил свои тесты на использование async для всех, изменения не возвращались назад. Я переключил SetUp
с async Task
на void
, и он начал вести себя как ожидалось.
Короткий вопрос, полученный от случая
При использовании TransactionScope с асинхронным/Await, вам нужно создать SqlConnection в том же потоке, что и TransactionScope для того, чтобы распространить на все последующие асинхронные операции?
Long Вопрос
.NET добавил TransactionScopeAsyncFlowOption к TransactionScope и описал его как контроль «является ли сделка окружающей среды, связанная с объемом транзакций будет проходить через нить продолжения»
на основе поведения я вижу , похоже, что вам все равно нужно создать экземпляр SqlConnections в корневом потоке TransactionScope, иначе команды не будут автоматически зачислены в транзакцию окружающего пространства. Это имеет механический смысл, я просто не могу найти его нигде в документации. Так что я думаю, мне интересно, знает ли кто-нибудь немного больше о предмете?
Это тестовый пример (использует NUnit и Dapper) Я пришел, пытаясь выяснить, что происходит с моей конкретной проблемой, которая была таймаутом bc, таблица заблокирована транзакцией, мое второе соединение не зачислен в (я думаю?)
NUnit связанные примечания: если вы тестируете код асинхронной и вы хотите запустить все внутри TransactionScope, не сделать свой [Настройка] метод асинхронной задачи. Если вы это сделаете, он, вероятно, будет работать в отдельном потоке из вашего фактического метода тестирования, и ваши соединения не будут зачислены в транзакцию.
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);
}
}
}
Вы можете явно связать команды с соответствующей транзакцией? – Mobigital
Обратите внимание на используемую среду выполнения .NET. –
TransactionScope не влияет на асинхронные операции. Этот параметр необходим, потому что 'TransactionScope' не сохранялся' await' в контексте синхронизации в предыдущих версиях. ** Код теста ** открывает соединение в другом потоке с помощью 'Task.StartNew'. Нет никакого контекста для сохранения. * Почему вы делаете это вместо использования 'SqlConnection.OpenAsync()'? –