Udostępnij przez


Integracja transakcji System.Transactions z programem SQL Server

Dotyczy: .NET Framework .NET Standard

Pobieranie ADO.NET

Platforma .NET zawiera 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 na platformie .NET, w tym ADO.NET.

Oprócz ulepszeń w zakresie możliwości programowania, System.Transactions i ADO.NET mogą współpracować, aby koordynować optymalizacje podczas pracy z transakcjami. Transakcja, którą można promować, to lekka (lokalna) transakcja, która może być automatycznie promowana do w pełni rozproszonej transakcji w razie potrzeby.

Dostawca danych Microsoft SqlClient dla programu SQL Server obsługuje transakcje promotable podczas pracy z programem SQL Server. Transakcja promowalna nie powoduje dodatkowego obciążenia związanego z transakcjami rozproszonymi, chyba że dodatkowe obciążenie jest konieczne. Transakcje promocyjne są automatyczne i nie wymagają interwencji dewelopera.

Tworzenie transakcji promocyjnych

Dostawca danych Microsoft SqlClient dla programu SQL Server zapewnia obsługę transakcji promowalnych, które są obsługiwane za pośrednictwem klas w System.Transactions przestrzeni nazw. Transakcje promowalne optymalizują transakcje rozproszone przez odroczenie ich tworzenia aż do momentu, gdy jest to niezbędne. Jeśli wymagany jest tylko jeden menedżer zasobów, żadna transakcja rozproszona nie występuje.

Uwaga / Notatka

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

Promowalne scenariusze transakcji

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, Microsoft.Data.SqlClient i SQL Server koordynują pracę związaną z obsługą transakcji, podnosząc ją do poziomu pełnej transakcji rozproszonej zgodnie z potrzebami.

Zaletą korzystania z transakcji możliwych do promocji jest to, że po otwarciu połączenia przy użyciu aktywnej TransactionScope transakcji i braku otwarcia innych połączeń, transakcja jest zatwierdzana jako lekka transakcja, zamiast ponosić dodatkowe obciążenie związane z pełną transakcją rozproszoną.

Słowa kluczowe parametrów połączenia

Właściwość ConnectionString obsługuje słowo kluczowe Enlist, które wskazuje, czy Microsoft.Data.SqlClient wykryje konteksty transakcyjne i automatycznie zarejestruje połączenie w transakcji rozproszonej. Jeśli Enlist=true, połączenie jest automatycznie rejestrowane w bieżącym kontekście transakcji wątku otwierającego. Jeśli Enlist=false, połączenie SqlClient nie wchodzi w interakcję z transakcją rozproszoną. Wartość domyślna parametru Enlist to true. Jeśli Enlist nie zostanie określony w parametrach połączenia, połączenie zostanie automatycznie umieszczone w transakcji rozproszonej, jeśli zostanie wykryte po otwarciu połączenia.

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

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

Keyword Description
Niejawne powiązanie Wartość domyślna. Połączenie odłącza się od transakcji po jej zakończeniu, przełączając się z powrotem do trybu automatycznego zatwierdzania.
Jawne rozłączenie 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 TransactionScope

Klasa TransactionScope czyni blok kodu transakcyjnym, automatycznie rejestrując połączenia w transakcji rozproszonej. Przed opuszczeniem bloku należy wywołać metodę Complete na jego końcu. Opuszczenie bloku wywołuje metodę Dispose. Jeśli zgłoszono wyjątek, który powoduje opuszczenie zakresu przez kod, transakcja zostanie uznana za przerwaną.

Zalecamy użycie bloku using, aby upewnić się, że Dispose jest wywoływany na obiekcie TransactionScope po wyjściu z bloku using. 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ć metodę Dispose w Finally bloku.

Jeśli wystąpi wyjątek w TransactionScope, transakcja jest oznaczona jako niespójna i jest porzucana. Zostanie on wycofany, gdy TransactionScope zostanie usunięty. Jeśli nie wystąpi wyjątek, uczestniczące transakcje zostaną zatwierdzone.

Uwaga / Notatka

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

Uwaga / Notatka

Zalecamy wykonywanie tylko aktualizacji, wstawiania i usuwania w ramach transakcji rozproszonych, ponieważ zużywają one znaczne zasoby bazy danych. Zapytania select mogą niepotrzebnie blokować zasoby bazy danych, a w niektórych scenariuszach może być konieczne użycie transakcji dla instrukcji select. 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. Chociaż wyjątek w zakresie transakcji uniemożliwia zatwierdzenie transakcji, klasa TransactionScope nie zapewnia możliwości cofnięcia 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.

Example

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

Poniższa funkcja pokazuje, jak utworzyć transakcję, którą można promować, dla dwóch różnych wystąpień programu SQL Server reprezentowanych przez dwa różne SqlConnection obiekty, które są opakowane w bloku TransactionScope.

Poniższy kod tworzy blok TransactionScope z instrukcją using i otwiera pierwsze połączenie, które dodaje je automatycznie do TransactionScope.

Transakcja jest początkowo zarejestrowana jako lekka transakcja, a nie pełnoprawna transakcja rozproszona. Drugie połączenie zostaje wymienione w TransactionScope tylko wtedy, 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.

Później metoda Complete jest wywoływana, która zatwierdza transakcję tylko wtedy, gdy nie zostały zgłoszone żadne wyjątki. Jeśli w dowolnym momencie w bloku TransactionScope zostanie zgłoszony wyjątek, Complete nie zostanie wywołany, a transakcja rozproszona zostanie wycofana, gdy TransactionScope zostanie zlikwidowane na końcu bloku using.

using System;
using System.Transactions;
using Microsoft.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = AdventureWorks";

        string commandText1 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong size')";
        string commandText2 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong color')";

        int result = CreateTransactionScope(connectionString, connectionString, commandText1, commandText2);

        Console.WriteLine("result = " + result);
    }

    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;
    }
}

Zobacz także