Integracja transakcji System.Transactions z programem SQL Server

Program .NET Framework w wersji 2.0 wprowadził platformę transakcji, do których można uzyskać dostęp za pośrednictwem System.Transactions przestrzeni nazw. Ta struktura uwidacznia transakcje w sposób w pełni zintegrowany w programie .NET Framework, w tym ADO.NET.

Oprócz ulepszeń System.Transactions możliwości programowania i ADO.NET mogą współpracować w celu koordynowania optymalizacji podczas pracy z transakcjami. Transakcja promotable to uproszczona (lokalna) transakcja, która może być automatycznie promowana do w pełni rozproszonej transakcji zgodnie z potrzebami.

Począwszy od ADO.NET 2.0, System.Data.SqlClient obsługuje transakcje promocyjne podczas pracy z programem SQL Server. Transakcja promotable nie wywołuje dodatkowego obciążenia transakcji rozproszonej, chyba że dodatkowe obciążenie jest wymagane. Transakcje promocyjne są automatyczne i nie wymagają interwencji dewelopera.

Transakcje promocyjne są dostępne tylko w przypadku korzystania z programu .NET Framework Dostawca danych dla programu SQL Server (SqlClient) z programem SQL Server.

Tworzenie transakcji promocyjnych

Dostawca programu .NET Framework dla programu SQL Server zapewnia obsługę transakcji promotable, które są obsługiwane za pośrednictwem klas w przestrzeni nazw programu .NET Framework System.Transactions . Transakcje promotable optymalizują transakcje rozproszone przez odroczenie tworzenia transakcji rozproszonej, dopóki nie będzie potrzebne. Jeśli wymagany jest tylko jeden menedżer zasobów, żadna transakcja rozproszona nie występuje.

Uwaga

W częściowo zaufanym scenariuszu jest to wymagane, DistributedTransactionPermission gdy transakcja jest promowana do transakcji rozproszonej.

Scenariusze transakcji promotable

Transakcje rozproszone zwykle zużywają znaczne zasoby systemowe, które są zarządzane przez koordynatora transakcji rozproszonych firmy Microsoft (MS DTC), który integruje wszystkich menedżerów zasobów, do których uzyskuje dostęp w transakcji. Transakcja promotable jest specjalną formą System.Transactions transakcji, która skutecznie deleguje pracę do prostej transakcji programu SQL Server. System.Transactions, System.Data.SqlClienti SQL Server koordynują pracę związaną z obsługą transakcji, promując ją do pełnej transakcji rozproszonej zgodnie z potrzebami.

Zaletą korzystania z transakcji promotable jest to, że po otwarciu połączenia przy użyciu aktywnej TransactionScope transakcji, a żadne inne połączenia nie są otwarte, transakcje są zatwierdzane jako uproszczona transakcja, zamiast ponosić dodatkowe koszty związane z pełną transakcją rozproszoną.

Słowa kluczowe parametrów połączenia

Właściwość ConnectionString obsługuje słowo kluczowe , które wskazuje, Enlistczy System.Data.SqlClient wykryje konteksty transakcyjne i automatycznie zarejestrowa połączenie w transakcji rozproszonej. Jeśli Enlist=trueparametr , połączenie jest automatycznie wyświetlane w bieżącym kontekście transakcji wątku otwierającego. Jeśli Enlist=falsepołączenie nie wchodzi w interakcję SqlClient z transakcją rozproszoną. Wartość domyślna parametru Enlist to true. Jeśli Enlist nie zostanie określony w parametry połączenia, połączenie zostanie automatycznie umieszczone w transakcji rozproszonej, jeśli zostanie wykryte podczas otwierania połączenia.

Słowa Transaction Binding kluczowe w SqlConnection parametry połączenia kontrolują skojarzenie połączenia z transakcją enlistedSystem.Transactions. Jest on również dostępny za pośrednictwem TransactionBinding właściwości .SqlConnectionStringBuilder

W poniższej tabeli opisano możliwe wartości.

Słowo kluczowe opis
Niejawne powiązanie Domyślnie. Połączenie odłącza się od transakcji po jej zakończeniu, przełączając się z powrotem do trybu automatycznego zatwierdzania.
Jawne, bez powiązania Połączenie pozostaje dołączone do transakcji do momentu zamknięcia transakcji. Połączenie zakończy się niepowodzeniem, jeśli skojarzona transakcja nie jest aktywna lub nie jest zgodna Currentz parametrem .

Korzystanie z elementu TransactionScope

Klasa TransactionScope sprawia, że blok kodu transakcyjny przez niejawne rejestrowanie połączeń w transakcji rozproszonej. Przed opuszczeniem tej metody należy wywołać metodę Complete na końcu TransactionScope bloku. Pozostawienie bloku wywołuje metodę Dispose . Jeśli zgłoszono wyjątek, który powoduje pozostawienie zakresu przez kod, transakcja zostanie uznana za przerwaną.

Zalecamy użycie using bloku, aby upewnić się, że Dispose obiekt jest wywoływany po zakończeniu TransactionScope korzystania z bloku. Niepowodzenie zatwierdzania lub wycofywania oczekujących transakcji może znacząco uszkodzić wydajność, ponieważ domyślny limit czasu dla wartości TransactionScope wynosi jedną minutę. Jeśli nie używasz using instrukcji, musisz wykonać całą pracę w Try bloku i jawnie wywołać Dispose metodę Finally w bloku.

Jeśli wystąpi wyjątek w TransactionScopeobiekcie , transakcja zostanie oznaczona jako niespójna i zostanie porzucona. Zostanie on wycofany, gdy TransactionScope zostanie usunięty. Jeśli nie wystąpi wyjątek, zatwierdzanie uczestniczących transakcji.

Uwaga

Klasa TransactionScope domyślnie tworzy transakcję z wartością IsolationLevel Serializable . W zależności od aplikacji warto rozważyć obniżenie poziomu izolacji, aby uniknąć wysokiej rywalizacji w aplikacji.

Uwaga

Zalecamy wykonywanie tylko aktualizacji, wstawiania i usuwania w ramach transakcji rozproszonych, ponieważ zużywają one znaczne zasoby bazy danych. Instrukcje select mogą niepotrzebnie blokować zasoby bazy danych, a w niektórych scenariuszach może być konieczne użycie transakcji do wyboru. Każda praca niezwiązana z bazą danych powinna odbywać się poza zakresem transakcji, chyba że obejmuje ona innych transakcyjnych menedżerów zasobów. Mimo że wyjątek w zakresie transakcji uniemożliwia zatwierdzanie transakcji, TransactionScope klasa nie ma aprowizacji wycofywania wszelkich zmian wprowadzonych w kodzie poza zakresem samej transakcji. Jeśli musisz podjąć jakąś akcję, gdy transakcja zostanie wycofana, musisz napisać własną implementację interfejsu IEnlistmentNotification i jawnie zarejestrować się w transakcji.

Przykład

Praca z System.Transactions programem wymaga odwołania do System.Transactions.dll.

Poniższa funkcja pokazuje, jak utworzyć transakcję promotable dla dwóch różnych wystąpień programu SQL Server reprezentowanych przez dwa różne SqlConnection obiekty, które są opakowane w TransactionScope bloku. Kod tworzy TransactionScope blok za pomocą using instrukcji i otwiera pierwsze połączenie, które automatycznie zapisuje go w pliku TransactionScope. Transakcja jest początkowo wymieniona jako uproszczona transakcja, a nie pełna transakcja rozproszona. Drugie połączenie jest wymienione tylko wtedy TransactionScope , gdy polecenie w pierwszym połączeniu nie zgłasza wyjątku. Po otwarciu drugiego połączenia transakcja jest automatycznie promowana do pełnej transakcji rozproszonej. Metoda Complete jest wywoływana, która zatwierdza transakcję tylko wtedy, gdy nie zostały zgłoszone żadne wyjątki. Jeśli w jakimkolwiek momencie TransactionScope bloku zostanie zgłoszony wyjątek, Complete nie zostanie wywołany, a transakcja rozproszona zostanie wycofana po TransactionScope usunięciu na końcu bloku using .

// This function takes arguments for the 2 connection strings and commands in order
// to create a transaction involving two SQL Servers. It returns a value > 0 if the
// transaction committed, 0 if the transaction 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 RDBMS such as Oracle 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();

    // Create the TransactionScope in which to execute the commands, guaranteeing
    // that both commands will commit or roll back as a single unit of work.
    using (TransactionScope scope = new TransactionScope())
    {
        using (SqlConnection connection1 = new SqlConnection(connectString1))
        {
            try
            {
                // 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 by opening connection2
                // only when there is a chance that the transaction can commit.
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                    try
                    {
                        // The transaction is promoted 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);
                    }
                    catch (Exception ex)
                    {
                        // Display information that command2 failed.
                        writer.WriteLine("returnValue for command2: {0}", returnValue);
                        writer.WriteLine("Exception Message2: {0}", ex.Message);
                    }
            }
            catch (Exception ex)
            {
                // Display information that command1 failed.
                writer.WriteLine("returnValue for command1: {0}", returnValue);
                writer.WriteLine("Exception Message1: {0}", ex.Message);
            }
        }

        // If an exception has been thrown, Complete will not
        // be called and the transaction is rolled back.
        scope.Complete();
    }

    // The returnValue is greater than 0 if the transaction committed.
    if (returnValue > 0)
    {
        writer.WriteLine("Transaction was committed.");
    }
    else
    {
        // You could write additional business logic here, notify the caller by
        // throwing a TransactionAbortedException, or log the failure.
        writer.WriteLine("Transaction rolled back.");
    }

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

    return returnValue;
}
' This function takes arguments for the 2 connection strings and commands in order
' to create a transaction involving two SQL Servers. It returns a value > 0 if the
' transaction committed, 0 if the transaction 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 RDBMS such as Oracle 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

    ' Create the TransactionScope in which to execute the commands, guaranteeing
    ' that both commands will commit or roll back as a single unit of work.
    Using scope As New TransactionScope()
        Using connection1 As New SqlConnection(connectString1)
            Try
                ' 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 by opening connection2
                ' only when there is a chance that the transaction can commit.
                Using connection2 As New SqlConnection(connectString2)
                    Try
                        ' The transaction is promoted 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)

                    Catch ex As Exception
                        ' Display information that command2 failed.
                        writer.WriteLine("returnValue for command2: {0}", returnValue)
                        writer.WriteLine("Exception Message2: {0}", ex.Message)
                    End Try
                End Using

            Catch ex As Exception
                ' Display information that command1 failed.
                writer.WriteLine("returnValue for command1: {0}", returnValue)
                writer.WriteLine("Exception Message1: {0}", ex.Message)
            End Try
        End Using

        ' If an exception has been thrown, Complete will
        ' not be called and the transaction is rolled back.
        scope.Complete()
    End Using

    ' The returnValue is greater than 0 if the transaction committed.
    If returnValue > 0 Then
        writer.WriteLine("Transaction was committed.")
    Else
        ' You could write additional business logic here, notify the caller by
        ' throwing a TransactionAbortedException, or log the failure.
       writer.WriteLine("Transaction rolled back.")
     End If

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

    Return returnValue
End Function

Zobacz też