Sdílet prostřednictvím


Implementace implicitní transakce pomocí oboru transakce

Třída TransactionScope poskytuje jednoduchý způsob, jak označit blok kódu jako účast v transakci, aniž byste museli interagovat se samotnou transakcí. Obor transakce může vybrat a spravovat okolí transakce automaticky. Vzhledem k jeho snadnému použití a efektivitě se doporučuje používat TransactionScope třídu při vývoji transakční aplikace.

Kromě toho nemusíte prostředky explicitně zapojovat do transakce. Jakýkoli System.Transactions správce prostředků (například SQL Server 2005) může zjistit existenci okolní transakce vytvořené oborem a automaticky se zapojit.

Vytvoření rozsahu transakce

Následující ukázka ukazuje jednoduché použití TransactionScope třídy.

// 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

Jakmile vytvoříte nový TransactionScope objekt, spustí se obor transakce. Jak je znázorněno v ukázce kódu, doporučujeme vytvořit obory pomocí using příkazu. Příkaz using je k dispozici v jazyce C# i v jazyce Visual Basic a funguje jako tryblok ...finally a zajišťuje, aby byl obor správně uvolněn.

Při vytváření instance TransactionScope, správce transakcí určuje, která transakce se má účastnit. Po určení se rozsah vždy účastní této transakce. Rozhodnutí je založeno na dvou faktorech: zda je okrajová transakce přítomna a na hodnotě parametru TransactionScopeOption v konstruktoru. Transakce prostředí je transakce, ve které váš kód probíhá. Odkaz na ambientní transakci lze získat voláním statické vlastnosti Transaction.Current třídy Transaction. Další informace o tom, jak se tento parametr používá, naleznete v části Správa toku transakce pomocí TransactionScopeOption oddílu tohoto tématu.

Dokončení rozsahu transakce

Když aplikace dokončí veškerou práci, kterou chce provést v transakci, měli byste volat metodu TransactionScope.Complete pouze jednou, aby informoval správce transakcí, že je přijatelné potvrdit transakci. Je velmi vhodné volat Complete jako poslední příkaz v using bloku.

Neschopnost zavolat tuto metodu přeruší transakci, protože správce transakcí to interpretuje jako selhání systému nebo ekvivalentní výjimce vyvolané v rámci této transakce. Volání této metody však nezaručuje, že transakce bude potvrzena. Jedná se pouze o způsob, jak informovat správce transakcí o vašem stavu. Po volání Complete metody již nelze přistupovat k okolní transakci pomocí Current vlastnosti, a pokud se o to pokusíte, bude vyhozena výjimka.

TransactionScope Pokud objekt vytvořil transakci zpočátku, skutečná práce potvrzení transakce správce transakcí nastane po posledním řádku kódu v using bloku. Pokud se transakce nevytvořila, k potvrzení dojde vždy, když Commit je volána vlastníkem objektu CommittableTransaction . V daný okamžik správce transakcí volá správce prostředků a informuje je, zda by měli potvrdit nebo vrátit zpět na základě toho, zda byla metoda Complete volána na objektu TransactionScope.

Příkaz using zajišťuje, že Dispose metoda objektu TransactionScope je volána i v případě, že dojde k výjimce. Metoda Dispose označuje konec oboru transakce. Výjimky, ke kterým dochází po volání této metody, nemusí ovlivnit transakci. Tato metoda také obnoví prostředí transakce do předchozího stavu.

A TransactionAbortedException je vyvolána, pokud obor vytvoří transakci a transakce je přerušena. Vyvolá se TransactionInDoubtException, pokud správce transakcí nemůže dosáhnout rozhodnutí o potvrzení. Není vyvolána žádná výjimka, pokud je transakce potvrzena.

Vrácení transakce zpět

Pokud chcete vrátit zpět transakci, neměli byste volat metodu Complete v rámci oboru transakce. Můžete například vyvolat výjimku v rámci oboru. Transakce, ve které se účastní, bude vrácena zpět.

Správa toku transakcí pomocí TransactionScopeOption

Rozsah transakce může být vnořený voláním metody, která používá TransactionScope z metody, která používá vlastní rozsah, jak je tomu v případě metody RootMethod v následujícím příkladu.

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

Nejvyšší rozsah transakcí se označuje jako kořenový obor.

TransactionScope Třída poskytuje několik přetížených konstruktorů, které přijímají výčet typu TransactionScopeOption, který definuje transakční chování oboru.

Objekt TransactionScope má tři možnosti:

  • Připojte se k ambientní transakci nebo vytvořte novou transakci, pokud žádná neexistuje.

  • Vytvořit nový kořenový obor platnosti, to znamená spustit novou transakci a tato transakce bude novou okolní transakcí ve svém vlastním oboru.

  • Neúčastněte se vůbec transakce. V důsledku toho neexistuje žádná okrajová transakce.

Pokud je rozsah vytvořen s Required a okolní transakce je přítomná, rozsah se připojí k této transakci. Pokud na druhou stranu neexistuje žádná okolní transakce, pak tento obor vytvoří novou transakci a stane se kořenovým oborem. Toto je výchozí hodnota. Při použití Required se kód uvnitř oboru nemusí chovat jinak, ať už je to kořen nebo pouze připojení k okolní transakci. Měla by fungovat stejně v obou případech.

Pokud se obor vytvoří instance s RequiresNew, je to vždy kořenový obor. Spustí novou transakci a tato transakce se stane novou kontextovou transakcí v rámci oboru.

Je-li rozsah vytvořen instancí s Suppress, nikdy se neúčastní transakce, bez ohledu na to, zda je okolní transakce přítomna. Vytvořený obor s touto hodnotou má vždy null jako svou okolní transakci.

Výše uvedené možnosti jsou shrnuty v následující tabulce.

Možnost rozsahu transakce Ambientní transakce Rozsah zahrnuje
Povinné Ne Nová transakce (bude kořenem)
Vyžaduje něco nového Ne Nová transakce (bude kořenem)
Potlačit Ne Žádná transakce
Povinné Ano Ambientní transakce
Vyžaduje něco nového Ano Nová transakce (bude kořenem)
Potlačit Ano Žádná transakce

TransactionScope Když se objekt připojí k existující okolní transakci, uvolnění objektu oboru nemusí ukončit transakci, pokud obor nepřeruší transakci. Pokud byla okolní transakce vytvořena kořenovým oborem, je Commit volána na transakci teprve poté, co je tento kořenový obor uvolněn. Pokud byla transakce vytvořena ručně, transakce skončí, když je buď přerušena, nebo potvrzena jeho tvůrcem.

Následující příklad ukazuje TransactionScope objekt, který vytváří tři vnořené oborové instance, z nichž každá je instancována s jinou TransactionScopeOption hodnotou.

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))
    {
        //...  
    }
}

Příklad ukazuje blok kódu bez jakéhokoli transakčního prostředí, k vytvoření nového oboru (scope1) po boku Required. Obor scope1 je kořenový obor, protože vytváří novou transakci (Transakce A) a činí ji okolní transakcí. Scope1 pak vytvoří tři další objekty, z nichž každá má jinou TransactionScopeOption hodnotu. Například scope2 je vytvořen s Required a vzhledem k tomu, že existuje okolní transakce, připojí se k první transakci vytvořené pomocí scope1. Všimněte si, že scope3 je kořenový obor nové transakce a že scope4 nemá žádnou okolní transakci.

I když je výchozí a nejčastěji používaná hodnota TransactionScopeOptionRequired, každá z ostatních hodnot má svůj jedinečný účel.

Neaktuální kód uvnitř oboru transakce

Suppress je užitečné, pokud chcete zachovat operace prováděné částí kódu a nechcete přerušit transakci prostředí, pokud operace selžou. Například když chcete provádět operace protokolování nebo auditu, nebo když chcete publikovat události odběratelům bez ohledu na to, zda se vaše okolní transakce potvrdí nebo zruší. Tato hodnota umožňuje mít oddíl netransakčního kódu uvnitř transakčního prostředí, jak je znázorněno v následujícím příkladu.

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
}

Hlasování ve vnořeném oboru

I když vnořený obor může spojit okolní transakci kořenového oboru, volání Complete ve vnořeném oboru nemá žádný vliv na kořenový obor. Transakce bude potvrzena pouze v případě, že všechny rozsahy, od kořenového rozsahu až po poslední vnořený rozsah, hlasují pro provedení transakce. Nevolání Complete ve vnořeném oboru ovlivní kořenový obor, protože okolní transakce bude okamžitě zrušena.

Nastavení časového limitu TransactionScope

Některé přetížené konstruktory TransactionScope přijímají hodnotu typu TimeSpan, která se používá k řízení časového limitu transakce. Časový limit nastavený na nulu znamená nekonečný časový limit. Neomezený časový limit je užitečný hlavně při ladění, když chcete izolovat problém ve své obchodní logice tím, že procházíte kód krok za krokem, a nechcete, aby transakce, kterou ladíte, vypršela v době, kdy se pokoušíte lokalizovat problém. Buďte velmi opatrní při používání nekonečné hodnoty časového limitu ve všech ostatních případech, protože přepíše ochranu proti zablokování transakcí.

Časový limit obvykle nastavíte TransactionScope na jiné hodnoty než výchozí ve dvou případech. První je během vývoje, když chcete otestovat způsob, jakým vaše aplikace zpracovává přerušené transakce. Nastavením časového limitu na malou hodnotu (například jednu milisekundu) způsobíte selhání transakce a můžete tak sledovat kód zpracování chyb. Druhý případ, kdy nastavíte hodnotu tak, aby byla menší než výchozí časový limit, nastává, když se domníváte, že rozsah je zapojen do soutěžení o prostředky, což může vést k zablokování. V takovém případě chcete transakci co nejdříve přerušit a nečekat na vypršení výchozího časového limitu.

Když se obor připojí k okolní transakci, ale určuje menší časový limit, než je nastavena okolní transakce, nový, kratší časový limit je vynucen na TransactionScope objektu a obor musí skončit v rámci zadaného kratšího času, jinak bude transakce automaticky ukončena. Pokud časový limit vnořeného oboru je větší než časový limit okolní transakce, nemá žádný vliv.

Nastavení úrovně izolace TransactionScope

Některé přetížené konstruktory TransactionScope umožňují zadat strukturu typu TransactionOptions k určení úrovně izolace a také časového limitu. Ve výchozím nastavení se transakce provádí s úrovní izolace nastavenou na Serializable. Výběr jiné úrovně izolace než Serializable se běžně používá pro systémy náročné na čtení. To vyžaduje solidní pochopení teorie zpracování transakcí a sémantiky samotné transakce, problémy souběžnosti a důsledky pro konzistenci systému.

Kromě toho ne všichni správci prostředků podporují všechny úrovně izolace a mohou se rozhodnout, že se budou účastnit transakce na vyšší úrovni, než je nakonfigurovaná.

Každá úroveň izolace kromě toho Serializable je náchylná k nekonzistence vyplývající z jiných transakcí, které přistupují ke stejným informacím. Rozdíl mezi různými úrovněmi izolace spočívá v tom, jak se používají zámky čtení a zápisu. Zámek může být uložen pouze v případě, že transakce přistupuje k datům ve Správci prostředků, nebo se může uchovávat, dokud transakce nebude potvrzena nebo přerušena. První z nich je lepší pro propustnost, druhá pro konzistenci. Dva druhy zámků a dva druhy operací (čtení/zápis) poskytují čtyři základní úrovně izolace. Další informace naleznete na IsolationLevel.

Při použití vnořených TransactionScope objektů musí být všechny vnořené obory nakonfigurovány tak, aby používaly přesně stejnou úroveň izolace, pokud chtějí připojit se k okolní transakci. Pokud se vnořený TransactionScope objekt pokusí připojit k okolní transakci, ale určuje jinou úroveň izolace, dojde k vyvolání ArgumentException.

Interoperabilita s modelem COM+

Při vytváření nové instance TransactionScope můžete pomocí výčtu EnterpriseServicesInteropOption v jednom z konstruktorů určit, jak pracovat s COM+. Další informace najdete v tématu Interoperabilita se službami Enterprise Services a transakcemi modelu COM+.

Viz také