Implementowanie transakcji niejawnej przy użyciu zakresu transakcji

Klasa TransactionScope zapewnia prosty sposób oznaczania bloku kodu jako udziału w transakcji bez konieczności interakcji z samą transakcją. Zakres transakcji można wybrać i automatycznie zarządzać otoczenia transakcji. Ze względu na łatwość użycia i wydajność zaleca się użycie TransactionScope klasy podczas tworzenia aplikacji transakcyjnej.

Ponadto nie trzeba jawnie zarejestrować zasobów z transakcją. Wszelkie System.Transactions Menedżera zasobów (takich jak SQL Server 2005) można wykryć istnienie otoczenia transakcji utworzone przez zakres i automatycznie zarejestrować.

Tworzenie zakresu transakcji

Poniższy przykład przedstawia proste użycie TransactionScope klasy.

// This function takes arguments for 2 connection strings and commands to create a transaction
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
// transaction is rolled back. To test this code, you can connect to two different databases
// on the same server by altering the connection string, or to another 3rd party RDBMS by
// altering the code in the connection2 code block.
static public int CreateTransactionScope(
    string connectString1, string connectString2,
    string commandText1, string commandText2)
{
    // Initialize the return value to zero and create a StringWriter to display results.
    int returnValue = 0;
    System.IO.StringWriter writer = new System.IO.StringWriter();

    try
    {
        // Create the TransactionScope to execute the commands, guaranteeing
        // that both commands can commit or roll back as a single unit of work.
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                // Opening the connection automatically enlists it in the
                // TransactionScope as a lightweight transaction.
                connection1.Open();

                // Create the SqlCommand object and execute the first command.
                SqlCommand command1 = new SqlCommand(commandText1, connection1);
                returnValue = command1.ExecuteNonQuery();
                writer.WriteLine("Rows to be affected by command1: {0}", returnValue);

                // If you get here, this means that command1 succeeded. By nesting
                // the using block for connection2 inside that of connection1, you
                // conserve server and network resources as connection2 is opened
                // only when there is a chance that the transaction can commit.
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                {
                    // The transaction is escalated to a full distributed
                    // transaction when connection2 is opened.
                    connection2.Open();

                    // Execute the second command in the second database.
                    returnValue = 0;
                    SqlCommand command2 = new SqlCommand(commandText2, connection2);
                    returnValue = command2.ExecuteNonQuery();
                    writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
                }
            }

            // The Complete method commits the transaction. If an exception has been thrown,
            // Complete is not  called and the transaction is rolled back.
            scope.Complete();
        }
    }
    catch (TransactionAbortedException ex)
    {
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
    }

    // Display messages.
    Console.WriteLine(writer.ToString());

    return returnValue;
}
'  This function takes arguments for 2 connection strings and commands to create a transaction 
'  involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the 
'  transaction is rolled back. To test this code, you can connect to two different databases 
'  on the same server by altering the connection string, or to another 3rd party RDBMS  
'  by altering the code in the connection2 code block.
Public Function CreateTransactionScope( _
  ByVal connectString1 As String, ByVal connectString2 As String, _
  ByVal commandText1 As String, ByVal commandText2 As String) As Integer

    ' Initialize the return value to zero and create a StringWriter to display results.
    Dim returnValue As Integer = 0
    Dim writer As System.IO.StringWriter = New System.IO.StringWriter

    Try
        ' Create the TransactionScope to execute the commands, guaranteeing
        '  that both commands can commit or roll back as a single unit of work.
        Using scope As New TransactionScope()
            Using connection1 As New SqlConnection(connectString1)
                ' Opening the connection automatically enlists it in the 
                ' TransactionScope as a lightweight transaction.
                connection1.Open()

                ' Create the SqlCommand object and execute the first command.
                Dim command1 As SqlCommand = New SqlCommand(commandText1, connection1)
                returnValue = command1.ExecuteNonQuery()
                writer.WriteLine("Rows to be affected by command1: {0}", returnValue)

                ' If you get here, this means that command1 succeeded. By nesting
                ' the using block for connection2 inside that of connection1, you
                ' conserve server and network resources as connection2 is opened
                ' only when there is a chance that the transaction can commit.   
                Using connection2 As New SqlConnection(connectString2)
                    ' The transaction is escalated to a full distributed
                    ' transaction when connection2 is opened.
                    connection2.Open()

                    ' Execute the second command in the second database.
                    returnValue = 0
                    Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2)
                    returnValue = command2.ExecuteNonQuery()
                    writer.WriteLine("Rows to be affected by command2: {0}", returnValue)
                End Using
            End Using

            ' The Complete method commits the transaction. If an exception has been thrown,
            ' Complete is called and the transaction is rolled back.
            scope.Complete()
        End Using
    Catch ex As TransactionAbortedException
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
    End Try

    ' Display messages.
    Console.WriteLine(writer.ToString())

    Return returnValue
End Function

Zakres transakcji jest uruchamiany po utworzeniu nowego TransactionScope obiektu. Jak pokazano w przykładzie kodu, zaleca się tworzenie zakresów za pomocą instrukcji using . Instrukcja using jest dostępna zarówno w języku C#, jak i w Visual Basic, i działa jak tryblok ...finally w celu zapewnienia prawidłowego usunięcia zakresu.

Podczas tworzenia instancji TransactionScope, Menedżer transakcji określa, która transakcja wziąć udział w. Po określeniu zakresu zawsze uczestniczy w danej transakcji. Decyzja opiera się na dwa czynniki: Określa, czy transakcja otoczenia jest obecny i ma wartość TransactionScopeOption parametr w konstruktorze. Transakcja otoczenia jest transakcji, w którym wykonuje kodu. Odwołanie do transakcji otoczenia można uzyskać przez wywołanie metody statyczne klasy Transaction.Current właściwości Transaction klasy. Aby uzyskać więcej informacji na temat sposobu używania tego parametru, zobacz sekcję Zarządzanie przepływem transakcji przy użyciu transactionScopeOption w tym temacie.

Kończenie zakresu transakcji

Gdy aplikacja ukończy całą pracę, którą chce wykonać w transakcji, należy wywołać TransactionScope.Complete metodę tylko raz, aby poinformować menedżera transakcji, że dopuszczalne jest zatwierdzenie transakcji. Bardzo dobrą praktyką jest umieszczenie wywołania Complete jako ostatniego using oświadczenia w bloku.

Nie można wywołać tej metody przerywa transakcję, ponieważ menedżer transakcji interpretuje to jako awarię systemu lub odpowiednik wyjątku zgłaszanego w zakresie transakcji. Jednak wywołanie tej metody nie gwarantuje, że transakcji będzie zatwierdzone. Jest tylko sposób informowania menedżera transakcji Twój status. Po wywołaniu Complete metody jest już dostępne otoczenia transakcji przy użyciu Current właściwości i próby podjęły spowodują wyjątek.

TransactionScope Jeśli obiekt utworzył transakcję początkowo, rzeczywista praca zatwierdzania transakcji przez menedżera transakcji występuje po ostatnim wierszu kodu w using bloku. Jeśli nie utworzył transakcji, zatwierdzanie występuje zawsze, gdy Commit jest wywoływana przez właściciela CommittableTransaction obiektu. W tym momencie menedżer transakcji wywołuje menedżerów zasobów i informuje ich o zatwierdzeniu lub wycofaniu TransactionScope na podstawie tegoComplete, czy metoda została wywołana w obiekcie.

Instrukcja using zapewnia, że Dispose metoda TransactionScope obiektu jest wywoływana, nawet jeśli wystąpi wyjątek. Dispose Metody oznacza koniec zakresu transakcji. Wyjątki, które mogą występować po wywołaniu tej metody nie może mieć wpływ na transakcji. Ta metoda również przywraca otoczenia transakcji jej poprzedniego stanu.

Element TransactionAbortedException jest generowany, jeśli zakres tworzy transakcji, a transakcja została przerwana. Element TransactionInDoubtException jest generowany, gdy Menedżer transakcji nie może podjąć decyzję zatwierdzania. Nie wyjątku, jeśli transakcja została zatwierdzona.

Wycofywanie transakcji

Jeśli chcesz wycofać transakcji, nie należy wywołać Complete metody w zakresie transakcji. Na przykład możesz zgłosić wyjątek w zakresie. Transakcji, w których uczestniczy w zostaną wycofane.

Zarządzanie przepływem transakcji przy użyciu transactionScopeOption

Zakres transakcji można zagnieżdżać przez wywołanie metody używającej TransactionScope metody z poziomu metody korzystającej z RootMethod własnego zakresu, tak jak w przypadku metody w poniższym przykładzie.

void RootMethod()
{
    using(TransactionScope scope = new TransactionScope())
    {
        /* Perform transactional work here */
        SomeMethod();
        scope.Complete();
    }
}

void SomeMethod()
{
    using(TransactionScope scope = new TransactionScope())
    {
        /* Perform transactional work here */
        scope.Complete();
    }
}

Zakres transakcji wierzchu nosi nazwę zakres głównego.

TransactionScope Udostępnia kilka przeciążenia konstruktorów, które akceptują wyliczenie typu TransactionScopeOption, definiujący transakcyjnych zachowanie zakresu.

Element TransactionScope obiekt zawiera trzy pozycje:

  • Dołącz do otoczenia transakcji lub Utwórz nową, jeśli nie istnieje.

  • Można nowego zakresu głównego, oznacza to, że uruchomić nowej transakcji i skonfigurować danej transakcji można nowej transakcji otoczenia w zakresie własnej.

  • Nie w ogóle brać udział w transakcji. Z tego względu nie ma żadnej otoczenia transakcji.

Jeśli zakres jest utworzone za pomocą elementów Requiredi transakcja otoczenia jest obecna, zakres sprzężenia danej transakcji. Jeśli z drugiej strony, nie ma żadnej transakcji otoczenia, następnie zakres tworzy nową transakcję i stają się zakres głównego. Jest to wartość domyślna. Gdy Required jest używany, kod w zakresie nie jest konieczne działają inaczej, czy jest to główny lub po prostu dołączenie do otoczenia transakcji. Powinna ona działać tak samo w obu przypadkach.

Jeśli zakres jest utworzone za pomocą elementów RequiresNew, zawsze jest zakres głównego. Rozpoczyna się nowej transakcji, a jego transakcji staje się nowe otoczenia transakcji w zakresie.

Jeśli zakres jest utworzone za pomocą elementów Suppress, nigdy nie bierze udział w transakcji, niezależnie od tego, czy transakcja otoczenia jest obecny. Zakres, który tworzy wystąpienie tej wartości, zawsze ma null wartość otoczenia transakcji.

Powyższych opcji przedstawiono w poniższej tabeli.

TransactionScopeOption Otoczenia transakcji Zakres uczestniczy
Wymagania Nie. Nowa transakcja (będzie główny)
Wymagane nowe Nie. Nowa transakcja (będzie główny)
Pomiń Nie. Nie transakcji
Wymagania Tak Otoczenia transakcji
Wymagane nowe Tak Nowa transakcja (będzie główny)
Pomiń Tak Nie transakcji

Gdy TransactionScope obiektu sprzężenia istniejącej transakcji otoczenia, usuwania obiektu zakres nie może kończyć się transakcji, chyba że zakres przerywa transakcję. Jeśli otoczenia transakcji został utworzony przez zakres głównego, tylko wtedy, gdy zakres główny jest usunięty, nie Commit jest wywoływana w transakcji. Jeśli transakcja została utworzona ręcznie, zakończenia transakcji, gdy jest to zostało przerwane lub przydzielonej przez jej twórcę.

W poniższym przykładzie pokazano TransactionScope obiekt, który tworzy trzy zagnieżdżone obiekty zakresu, z których każde tworzy wystąpienie z inną TransactionScopeOption wartością.

using(TransactionScope scope1 = new TransactionScope())
//Default is Required
{
    using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
    {
        //...
    }

    using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        //...  
    }
  
    using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        //...  
    }
}

W przykładzie pokazano blok kodu bez żadnej otoczenia transakcji tworzącej nowy zakres (scope1) za pomocą polecenia Required. Zakres scope1 jest zakresem głównego, ponieważ tworzy nową transakcję (transakcji A) i sprawia, że transakcja A otoczenia transakcji. Scope1następnie tworzy trzy więcej obiektów, każdy z inną TransactionScopeOption wartość. Na przykład scope2 jest tworzony za pomocą Requiredelementu , a ponieważ istnieje transakcja otoczenia, łączy pierwszą transakcję utworzoną przez scope1program . Należy pamiętać, że scope3 zakres główny nowej transakcji, a scope4 ma ma otoczenia transakcji.

Chociaż często używane wartości domyślne i większość TransactionScopeOption jest Required, inne wartości ma unikatowy z przeznaczeniem.

Kod nie transakcyjny wewnątrz zakresu transakcji

Suppressjest przydatne, gdy chcesz zachować operacji wykonywanych przez sekcję kodu, a nie chcesz przerwać otoczenia transakcji, jeśli operacje kończą się niepowodzeniem. Jeśli na przykład chcesz wykonać operacje rejestrowania lub inspekcji, lub gdy chcesz opublikować zdarzenia dla subskrybentów niezależnie od tego, czy otoczenia transakcji zatwierdzeń, czy przerwania. Ta wartość umożliwia utworzenie sekcji kodu nie transakcyjnego w zakresie transakcji, jak pokazano w poniższym przykładzie.

using(TransactionScope scope1 = new TransactionScope())
{
    try
    {
        //Start of non-transactional section
        using(TransactionScope scope2 = new
            TransactionScope(TransactionScopeOption.Suppress))  
        {  
            //Do non-transactional work here  
        }  
        //Restores ambient transaction here
   }
   catch {}  
   //Rest of scope1
}

Głosowanie wewnątrz zagnieżdżonej zakresu

Mimo że zagnieżdżony zakres może łączyć otoczenia transakcji zakresu głównego, wywołanie w Complete zagnieżdżonym zakresie nie ma wpływu na zakres główny. Transakcja zostanie zatwierdzona tylko wtedy, gdy wszystkie zakresy z zakresu głównego do ostatniego zagnieżdżonego zakresu zagłosują, aby zatwierdzić transakcję. Nie wywoływanie Complete w zagnieżdżonym zakresie będzie miało wpływ na zakres główny, ponieważ transakcja otoczenia zostanie natychmiast przerwana.

Ustawienie limitu czasu elementu TransactionScope

Niektóre z przeciążenia konstruktorów z TransactionScope akceptować wartości typu TimeSpan, używany do sterowania limit czasu transakcji. Upłynął limit czasu ustawioną wartość zero oznacza nieskończony limit czasu. Nieskończony limit czasu przydaje się głównie na potrzeby debugowania, gdy chcesz wyizolować problem w logiki biznesowej przez krokowe wykonywanie kodu, a użytkownik nie chce transakcji, które można debugować limitu czasu podczas próby zlokalizowania problemu. Ostrożność bardzo przy użyciu wartości nieskończony limit czasu we wszystkich innych przypadkach, ponieważ zastępuje zabezpiecza przed zakleszczenia transakcji.

Zwykle ustawiana TransactionScope limitu czasu w celu wartości innej niż domyślny w obu przypadkach. Pierwszy to podczas programowania, gdy chcesz przetestować sposób obsługi przerwanych transakcji przez aplikację. Przez ustawienie limitu czasu małej wartości (na przykład jeden milisekund), spowodować transakcji nie powiedzie się i w związku z tym można obserwować obsługę kodu błędu. Drugi przypadek, w którym można ustawić wartość jest mniejsza niż domyślna wartość limitu czasu jest, jeśli uważasz, że zakres polega konfliktu zasobów, co spowoduje zakleszczenia. W takim przypadku chcesz przerwać transakcji, jak najszybciej i nie czeka na domyślna wartość limitu czasu wygaśnięcia.

Gdy zakres sprzężenia otoczenia transakcji, ale określa limit czasu mniejsze niż ma ustawioną wartość transakcji otoczenia, nowe, krótszy limit czasu jest wymuszane na TransactionScope obiektu i zakres musi kończyć się w czasie zagnieżdżonych określonym lub transakcja jest automatycznie przerwana. Jeśli limit czasu zagnieżdżony zakres jest więcej niż otoczenia transakcji, ustawienie nie działa.

Ustawienie poziomu izolacji elementu TransactionScope

Niektóre z przeciążenia konstruktorów z TransactionScope zaakceptować struktury typu TransactionOptions określa poziom izolacji, oprócz wartość limitu czasu. Domyślnie transakcji wykonuje z ustawioną poziom izolacji Serializable. Wybranie innego niż poziom izolacji Serializable jest najczęściej używana w systemach intensywnie korzysta z odczytu. Wymaga to pełny opis przetwarzania interpretacją i semantyka transakcja współbieżności problemy związane z i skutków spójności systemu transakcji.

Ponadto nie wszystkie menedżerów zasobów obsługuje wszystkie poziomy izolacji i może zdecydować się do wzięcia udziału w transakcji na wyższym poziomie niż skonfigurowane.

Każdy poziom izolacji poza Serializable się niespójność wynikające z innych transakcji dostęp do tych samych informacji. Różnica między izolacji różne poziomy w ten sposób do odczytu i zapisu blokady są używane. Blokada może zostać pociągnięty tylko wtedy, gdy transakcji uzyskuje dostęp do danych w Menedżerze zasobów lub może zostać pociągnięty do momentu transakcji nie zostanie przekazana lub zostało przerwane. Jest lepsze przepustowości, ten ostatni w celu zachowania spójności. Dwa rodzaje blokady i dwa rodzaje operacji (odczyt/zapis) dostarcza cztery poziomy izolacji podstawowe. Aby uzyskać więcej informacji, zobacz IsolationLevel.

Po użyciu zagnieżdżone TransactionScope obiektów, wszystkie zakresy zagnieżdżonych musi być skonfigurowany do użycia dokładnie ten sam poziom izolacji, aby dołączyć otoczenia transakcji. Jeśli zagnieżdżonych TransactionScope obiektu próbuje dołączyć otoczenia transakcji, jeszcze określa poziom izolacji różnych ArgumentException zgłaszany.

Usługę międzyoperacyjną z modelu COM +

Podczas tworzenia nowego TransactionScope wystąpienie, można użyć EnterpriseServicesInteropOption wyliczenia w jednym z konstruktorów do określenia sposobu interakcji z modelu COM +. Aby uzyskać więcej informacji na ten temat, zobacz Współdziałanie z usługami przedsiębiorstwa i transakcjami COM+ .

Zobacz też