2017-02-07 6 views
3

Первого выпуск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); 
     } 
    } 
} 
+0

Вы можете явно связать команды с соответствующей транзакцией? – Mobigital

+1

Обратите внимание на используемую среду выполнения .NET. –

+1

TransactionScope не влияет на асинхронные операции. Этот параметр необходим, потому что 'TransactionScope' не сохранялся' await' в контексте синхронизации в предыдущих версиях. ** Код теста ** открывает соединение в другом потоке с помощью 'Task.StartNew'. Нет никакого контекста для сохранения. * Почему вы делаете это вместо использования 'SqlConnection.OpenAsync()'? –

ответ

0

На основе замечаний по моему ответу, в частности, ответ Панайотису Kanavos':

Опция асинхронной для TransactionScope вызывает TransactionScope быть переданы от задачи к задаче через контекст синхронизации. Тест-код в моем вопросе открыл соединение в другом потоке, чем TransactionScope был открыт с помощью Task.StartNew, поэтому не было контекста для передачи, никакой транзакции для распространения.

 Смежные вопросы

  • Нет связанных вопросов^_^