Implementace implicitní transakce s využitím oboru transakcí

TransactionScope Třída poskytuje jednoduchý způsob, jak označit bloku kódu jako účasti na transakci, aniž by bylo nutné k interakci se vlastní transakce. Obor transakce můžete vybrat a spravovat okolí transakce automaticky. Z důvodu jeho snadno použitelných a efektivitu, je doporučeno používat TransactionScope třídy při vývoji aplikace transakce.

Kromě toho není nutné zařazení prostředky explicitně s transakcí. Jakékoli System.Transactions můžete zjišťovat existenci transakci okolí Autor oboru a automaticky zařazení správce prostředků (například SQL Server 2005).

Vytváření oboru transakce

Následující příklad 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.

Když vytváříte instance TransactionScope, určuje správce transakcí, které transakci se účastnit programu. Poté, co bylo zjištěno, oboru vždy se účastní dané transakce. O tom, zda je založena na dva faktory: zda okolí transakce je přítomen a hodnota TransactionScopeOption parametr v konstruktoru. Okolí transakce je transakce, ve kterém se spustí váš kód. Odkaz na okolí transakce můžete získat voláním statické Transaction.Current vlastnost Transaction třídy. 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

Pokud vaše aplikace dokončí všechny pracovní chce provést v transakci, měli byste zavolat TransactionScope.Complete metoda pouze jednou informovat správce transakcí, že je přijatelné potvrzení transakce. Je velmi vhodné volat Complete jako poslední příkaz v using bloku.

Volání této metody přeruší transakci, protože správce transakcí to interpretuje jako selhání systému nebo ekvivalentní výjimce vyvolané v rámci transakce. Však voláním této metody není zaručit, že transakce probíhal v každé zemi být potvrzeny. Je pouze způsob, jak o tom bude informovat správce transakcí stavu. Po volání Complete v případě metody okolí transakce může již přistupovat pomocí Current vlastnost a pokusili, výsledkem bude výjimky.

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 nebyl vytvořen transakce, potvrzení dochází, pokud Commit je volána metodou vlastníka CommittableTransaction objektu. V tomto okamžiku správce transakcí volá správce prostředků a informuje je o potvrzení nebo vrácení zpět na základě toho, zda Complete byla metoda 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. Dispose Metoda označuje konec rozsahu transakce. Výjimky, k nimž došlo po volání této metody nemusí mít vliv na transakci. Tato metoda také obnoví okolí transakci ji předchozího stavu.

Objekt TransactionAbortedException je vyvolána, pokud obor vytvoří transakce a transakce je přerušená. Objekt TransactionInDoubtException je vyvolána, pokud správce transakcí nelze dosáhnout rozhodnutí o potvrzení. Pokud transakce není vyvolána žádná výjimka.

Vrácení transakce zpět

Pokud byste chtěli vrácení zpět transakcí, neměli by jste volat Complete metody v rozsahu transakce. Například může vyvolat výjimku v rámci oboru. Transakce, ve kterém je součástí bude vrácena zpět.

Správa toku transakcí pomocí TransactionScopeOption

Obor transakcí, které mohou být vnořené voláním metody, která používá TransactionScope z v rámci metody, která používá vlastní rozsah, jako je tomu u RootMethod metodu 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();
    }
}

Obor navrchu transakce se nazývá kořenového oboru.

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 objekt má tři možnosti:

  • Připojte se k okolí transakce nebo vytvořit novou, pokud neexistuje.

  • Být nových kořenový obor, to znamená, spusťte novou transakci a mají být nové okolí transakce v rámci vlastní rozsah transakce.

  • Neúčastní transakcí vůbec. V důsledku není žádná okolí transakce.

Pokud dojde k vytvoření oboru s Requireda okolí transakce je přítomen, oboru spojí dané transakce. Je-li na druhé straně není žádná okolí transakce, pak oboru vytvoří novou transakci a Staňte se kořenového oboru. Tato hodnota je výchozí. Při Required se používá, kód v rámci oboru není nutné, aby chovat odlišně, zda je kořenový adresář nebo právě připojuje okolí transakce. Měla pracovat stejně jako v obou případech.

Pokud dojde k vytvoření oboru s RequiresNew, je vždy kořenový oboru. Spustí novou transakci a jeho transakce se změní na nové okolí transakce v rámci oboru.

Pokud dojde k vytvoření oboru s Suppress, se nikdy účastní v transakci, bez ohledu na to zda okolí transakce je k dispozici. Obor vytvoří instanci s touto hodnotou vždy jako null její okolí transakce.

V následující tabulce je uveden výše uvedených možností.

TransactionScopeOption Okolí transakce Rozsah podílí na
Požaduje se No Nová transakce (bude kořenového adresáře)
Požaduje novou No Nová transakce (bude kořenového adresáře)
Potlačit No Žádná transakce
Požaduje se Ano Okolí transakce
Požaduje novou Ano Nová transakce (bude kořenového adresáře)
Potlačit Ano Žádná transakce

Když TransactionScope objekt spojí existující okolí transakce, uvolnění objektu oboru nesmí končit transakce, pokud obor zruší transakce. Pokud okolí transakce byla vytvořena pomocí kořenového oboru, pouze v případě, že je kořenový obor odstraněn, nemá Commit zavolána v transakci. Pokud transakce byla vytvořena ručně, transakce končí, když je buď bylo přerušeno nebo potvrzené jeho tvůrcem.

Následující příklad ukazuje TransactionScope objekt, který vytvoří tři vnořených objektů s rozsahem, každá instance s jiným TransactionScopeOption hodnotu.

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 bloku kódu, bez jakékoli okolí transakce vytváření nového oboru (scope1) s Required. Rozsah scope1 je kořenového oboru, který ji vytvoří novou transakci (transakce A) a usnadňuje transakce A okolí transakce. Scope1 pak vytvoří tři další objekty, z nichž každá má jinou TransactionScopeOption hodnotu. Můžete například scope2 je vytvořen s Required, a vzhledem k tomu, že existuje okolí transakce, spojení první transakce vytvořené scope1. Všimněte si, že scope3 kořenového oboru novou transakci a že je scope4 nemá žádné okolí transakce.

I když ve výchozím nastavení a většinu běžně používá hodnotu TransactionScopeOption je Required, všechny ostatní hodnoty, má své 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 okolí transakce, pokud operace selžou. Například když chcete provádět protokolování nebo auditovat operace, nebo když chcete publikovat události odběratelům bez ohledu na tom, zda váš okolí potvrzení nebo přerušení transakce. Tato hodnota slouží k mít část s kódem netransakční v rámci oboru transakcí, 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í uvnitř vnořené oboru

I když vnořený obor může spojit okolí transakce kořenového oboru, volání Complete v vnořený obor nemá žádný vliv na kořenový obor. Transakce bude potvrzena pouze v případě, že všechny obory, od kořenového oboru dolů do posledního vnořeného oboru, hlasovat pro potvrzení transakce. Complete Volání v vnořeného oboru ovlivní kořenový obor, protože okolí transakce bude okamžitě přerušena.

Nastavení časového limitu TransactionScope

Některé z přetížených konstruktorů z TransactionScope přijmout hodnoty typu TimeSpan, která se používá k řízení časový limit transakce. Časový limit nastaven na hodnotu nula znamená neomezený časový limit. Neomezený časový limit je užitečné většinou pro ladění, pokud chcete izolovat problém v obchodní logiky krokování kódu jazyka a nechcete, aby transakce, které můžete ladit vypršení časového limitu při pokusu o nalezení problému. Velmi Vyhněte se pomocí hodnoty neomezený časový limit ve všech ostatních případech, protože přepisuje ochranu proti zablokování transakce.

Obvykle nastavena TransactionScope časový limit na hodnoty jiné než výchozí v obou případech. První je během vývoje, pokud chcete testovat tak, jak vaše aplikace zpracovává přerušené transakce. Nastavením časového limitu na malou hodnotu (například jeden milisekund) způsobit selhání transakce a můžete tak sledovat svůj kód pro zpracování chyb. Druhý případ, ve kterém nastavíte hodnotu menší než výchozí časový limit je v případě, že budete mít dojem, oboru se zabývá konflikty prostředků, výsledkem zablokování. V takovém případě budete chtít přerušení transakce co nejdříve a čekat výchozí časový limit vypršení platnosti.

Pokud obor spojí okolí transakce, ale Určuje časový limit menší než okolí transakce je nastavena na, nový, kratší časový limit je vynucovat u TransactionScope objekt a obor musí končit v rámci vnořené dobu určenou, nebo je automaticky transakce zrušena. Je-li časový limit vnořené oboru je větší než který okolí transakce, nemá žádný vliv.

Nastavení zabezpečení na úroveň izolace TransactionScope

Některé z přetížených konstruktorů z TransactionScope přijmout strukturu typu TransactionOptions Chcete-li určit úroveň izolace kromě hodnotu časového limitu. Ve výchozím nastavení, provede transakce s nastavena na úroveň izolace Serializable. Jiné než výběrem úroveň izolace Serializable se obvykle používá pro čtení velkými nároky na výkon systémů. 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 nejsou všechny správce prostředků nepodporují všechny úrovně izolace a uživatelé mohou zvolit, zda k účasti v transakci na vyšší úrovni než který je nakonfigurován.

Každou úroveň izolace kromě Serializable se nekonzistence vyplývající z jiné transakce přístupu stejné informace. Rozdíl mezi úrovních různé izolace je způsobem, čtení a zápisu uzamčení se používají. Zámek mohou konat pouze v případě, že transakce přistupuje k datům ve Správci zdrojů nebo můžete budou konat, dokud nebude transakce potvrzena nebo zrušena. Je lepší pro propustnost ten konzistence. Dva druhy zámků a dva druhy operací (čtení a zápis) udává čtyři úrovně basic izolace. Další informace naleznete v tématu IsolationLevel.

Při použití vnořených TransactionScope objekty, musí být nakonfigurován obory všech vnořených používat přesně stejnou úroveň izolace, aby bylo možné připojit se k okolí transakce. Pokud vnořený TransactionScope objekt se pokusí připojit okolí transakce ještě určuje na úroveň izolace jiný ArgumentException je vyvolána.

Vzájemná funkční spolupráce s modelu COM +

Když vytvoříte nový TransactionScope instance, můžete použít EnterpriseServicesInteropOption výčet v jednom z konstruktorů k určení, jak pracovat s modelu COM +. Další informace najdete v tématu Interoperabilita se službami Enterprise Services a transakcemi modelu COM+.

Viz také