Поделиться через


Реализация явной транзакции с помощью класса CommittableTransaction

Класс CommittableTransaction позволяет приложениям использовать транзакцию явным образом вместо неявного использования с помощью класса TransactionScope. Он полезен при создании приложений, которым требуется использовать одну и ту же транзакцию в нескольких вызовах функций или нескольких вызовах потоков. В отличие от класса TransactionScope для фиксации или прерывания транзакции модуль записи приложения должен специально вызывать методы Commit и Rollback.

Общие сведения о классе CommittableTransaction

Класс CommittableTransaction наследуется от класса Transaction, поэтому он предоставляет все функциональные возможности последнего. Особенно полезным является метод Rollback класса Transaction, который также можно использовать для отката объекта CommittableTransaction.

Класс Transaction аналогичен классу CommittableTransaction , но не предлагает Commit метод. Это позволяет передавать объект транзакции (или его клоны) другим методам (возможно, в других потоках), управляя временем фиксации транзакции. Вызываемый код способен выполнять зачисление и голосовать за фиксацию или откат транзакции, но только создатель объекта CommittableTransaction может ее зафиксировать.

При работе с классом CommittableTransaction следует учитывать следующие факты.

  • Создание транзакции CommittableTransaction не задает внешнюю транзакцию. Чтобы диспетчеры ресурсов работали в правильном контексте транзакции, необходимо явно задать или сбросить внешнюю транзакцию. Задать текущую внешнюю транзакцию можно путем установки статического свойства Current глобального объекта Transaction.

  • Объект CommittableTransaction нельзя использовать повторно. После фиксации или отката объекта CommittableTransaction его нельзя повторно использовать в транзакции. Таким образом, его нельзя задать в качестве контекста текущей внешней транзакции.

Создание объекта CommittableTransaction

В следующем примере создается и фиксируется новый объект CommittableTransaction.

//Create a committable transaction
tx = new CommittableTransaction();

SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
SqlCommand myCommand = new SqlCommand();

//Open the SQL connection
myConnection.Open();

//Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx);

myCommand.Connection = myConnection;

// Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
myCommand.ExecuteNonQuery();

// Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
myCommand.ExecuteNonQuery();

// Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
myCommand.ExecuteNonQuery();

// Commit or rollback the transaction
while (true)
{
    Console.Write("Commit or Rollback? [C|R] ");
    ConsoleKeyInfo c = Console.ReadKey();
    Console.WriteLine();

    if ((c.KeyChar == 'C') || (c.KeyChar == 'c'))
    {
        tx.Commit();
        break;
    }
    else if ((c.KeyChar == 'R') || (c.KeyChar == 'r'))
    {
        tx.Rollback();
        break;
    }
}
myConnection.Close();
tx = null;
tx = New CommittableTransaction

Dim myConnection As New SqlConnection("server=(local)\SQLExpress;Integrated Security=SSPI;database=northwind")
Dim myCommand As New SqlCommand()

'Open the SQL connection
myConnection.Open()

'Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx)

myCommand.Connection = myConnection

'Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"
myCommand.ExecuteNonQuery()

'Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"
myCommand.ExecuteNonQuery()

'Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"
myCommand.ExecuteNonQuery()

'Commit or rollback the transaction
Dim c As ConsoleKeyInfo
While (True)
    Console.Write("Commit or Rollback? [C|R] ")
    c = Console.ReadKey()
    Console.WriteLine()

    If (c.KeyChar = "C") Or (c.KeyChar = "c") Then
        tx.Commit()
        Exit While
    ElseIf ((c.KeyChar = "R") Or (c.KeyChar = "r")) Then
        tx.Rollback()
        Exit While
    End If
End While

myConnection.Close()
tx = Nothing

Создание объекта CommittableTransaction не приводит к автоматическому заданию контекста внешней транзакции. Поэтому никакая операция над диспетчером ресурсов не является частью этой транзакции. Статическое свойство Current глобального объекта Transaction используется для задания или извлечения внешней транзакции, и приложению необходимо вручную задать ее, чтобы обеспечить возможность участия диспетчеров ресурсов в транзакции. Также рекомендуется сохранить прежнюю внешнюю транзакцию и восстановить ее после завершения использования объекта CommittableTransaction.

Чтобы зафиксировать транзакцию, необходимо явно вызвать метод Commit. Чтобы откатить транзакцию, необходимо вызвать метод Rollback. Важно отметить, что пока не выполнена фиксация или откат транзакции CommittableTransaction, все задействованные в ней ресурсы заблокированы.

Объект CommittableTransaction может использоваться внешними вызовами функций и потоками. Однако при этом разработчику приложения необходимо самостоятельно обеспечить обработку исключений и явный вызов метода Rollback(Exception) в случае сбоев.

Асинхронная фиксация

Класс CommittableTransaction также предоставляет механизм для асинхронной фиксации транзакций. Фиксация транзакции может занять значительное время, поскольку она может включать несколько операций доступа к базе данных и возможны задержки в сети. Во избежание взаимоблокировок в высокопроизводительных приложениях можно использовать механизм асинхронной фиксации для завершения операций транзакции как можно скорее и выполнения операции фиксации в качестве фоновой задачи. Это можно сделать с помощью методов BeginCommit и EndCommit класса CommittableTransaction.

Можно вызвать метод BeginCommit, чтобы передать задачу фиксации потоку из пула потоков. Кроме того, можно вызвать метод EndCommit, чтобы определить, была ли выполнена фиксация транзакции. Если фиксацию транзакции по какой-либо причине выполнить не удалось, метод EndCommit вызывает исключение. Если на момент вызова метода EndCommit транзакция не зафиксирована, вызов блокируется до фиксации или отката транзакции.

Самый простой способ выполнения асинхронной фиксации - предоставление метода обратного вызова, вызываемого при завершении фиксации. Однако метод EndCommit необходимо вызвать для исходного объекта CommittableTransaction, который использовался для активизации вызова. Чтобы получить этот объект, можно переадресовать параметр IAsyncResult метода обратного вызова, так как CommittableTransaction класс реализует IAsyncResult класс.

В следующем примере показано, как выполнить асинхронную фиксацию.

public void DoTransactionalWork()  
{  
     Transaction oldAmbient = Transaction.Current;  
     CommittableTransaction committableTransaction = new CommittableTransaction();  
     Transaction.Current = committableTransaction;  
  
     try  
     {  
          /* Perform transactional work here */  
          // No errors - commit transaction asynchronously  
          committableTransaction.BeginCommit(OnCommitted,null);  
     }  
     finally  
     {  
          //Restore the ambient transaction
          Transaction.Current = oldAmbient;  
     }  
}  
void OnCommitted(IAsyncResult asyncResult)  
{  
     CommittableTransaction committableTransaction;  
     committableTransaction = asyncResult as CommittableTransaction;
     Debug.Assert(committableTransaction != null);  
     try  
     {  
          using(committableTransaction)  
          {  
               committableTransaction.EndCommit(asyncResult);  
          }  
     }  
     catch(TransactionException e)  
     {  
          //Handle the failure to commit  
     }  
}  

См. также