Megosztás:


Implicit tranzakció megvalósítása tranzakció hatókörével

Az TransactionScope osztály egyszerű módot kínál a kódblokkok tranzakcióban való részvételként való megjelölésére anélkül, hogy magának a tranzakciónak a használatát kellene megkövetelnie. A tranzakció hatóköre automatikusan kiválaszthatja és kezelheti a környezeti tranzakciót. A könnyű használat és a hatékonyság miatt javasoljuk, hogy tranzakciós alkalmazás fejlesztésekor használja az TransactionScope osztályt.

Emellett nem kell explicit módon bevonnia az erőforrásokat a tranzakcióba. Bármely System.Transactions erőforrás-kezelő (például az SQL Server 2005) észleli a hatókör által létrehozott környezeti tranzakció meglétét, és automatikusan bevonja.

Tranzakció hatókörének létrehozása

Az alábbi minta az TransactionScope osztály egyszerű használatát mutatja be.

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

A tranzakció hatóköre egy új TransactionScope objektum létrehozása után indul el. Ahogy a kódmintában is látható, javasoljuk, hogy hozzon létre hatóköröket utasítással using . Az using utasítás elérhető mind C#, mind Visual Basic nyelven, és úgy működik, mint egy try...finally blokk, biztosítva, hogy a hatókör megfelelően legyen feloldva.

A példányosításkor TransactionScopea tranzakciókezelő határozza meg, hogy melyik tranzakcióban szeretne részt venni. A meghatározást követően a hatókör mindig részt vesz a tranzakcióban. A döntés két tényezőn alapul: hogy van-e környezeti tranzakció, és a paraméter értéke a TransactionScopeOption konstruktorban. A környezeti tranzakció az a tranzakció, amelyen belül a kód végrehajtása történik. A környezeti tranzakcióra mutató hivatkozást az osztály statikus Transaction.Current tulajdonságának Transaction meghívásával szerezheti be. A paraméter használatáról további információt a jelen témakör TransactionScopeOption használatával végzett tranzakciókezelés című szakaszában talál.

A tranzakció hatókörének lezárása

Ha az alkalmazás befejezi a tranzakcióban elvégezni kívánt összes feladatot, a metódust csak egyszer kell meghívnia TransactionScope.Complete , hogy tájékoztassa a tranzakciókezelőt, hogy elfogadható a tranzakció véglegesítése. Nagyon jó gyakorlat az, ha a Complete blokkban a using-hívást utolsó utasításként tesszük be.

A metódus meghívásának sikertelensége megszakítja a tranzakciót, mert a tranzakciókezelő rendszerhibaként értelmezi ezt, vagy egyenértékű a tranzakció hatókörébe tartozó kivétellel. A metódus meghívása azonban nem garantálja, hogy a tranzakció véglegesítése végbemenjen. Ez csupán egy módja annak, hogy tájékoztassa a tranzakciókezelőt az állapotáról. A metódus meghívása Complete után a továbbiakban nem férhet hozzá a környezeti tranzakcióhoz a Current tulajdonság használatával, és ennek megkísérlése kivételt eredményez.

Ha az TransactionScope objektum kezdetben létrehozta a tranzakciót, a tranzakció tranzakciókezelő általi véglegesítésének tényleges munkája a blokk utolsó kódsorát using követően történik. Ha nem ő hozta létre a tranzakciót, a véglegesítés akkor következik be, amikor az objektum tulajdonosa meghívja a Commit-t. Ezen a ponton a tranzakciókezelő meghívja az erőforrás-kezelőket, és tájékoztatja őket a véglegesítésről vagy a visszaállításról, attól függően, hogy a Complete metódust meghívták-e az TransactionScope objektumon.

Az using utasítás biztosítja, hogy az Dispose objektum metódusát akkor is meghívja a TransactionScope rendszer, ha kivétel történik. A Dispose metódus a tranzakció hatókörének végét jelöli. A metódus meghívása után előforduló kivételek nem feltétlenül befolyásolják a tranzakciót. Ez a módszer a környezeti tranzakciót is visszaállítja az előző állapotba.

A TransactionAbortedException akkor lesz eldobva, ha a hatókör létrehozza a tranzakciót, és a tranzakció megszakad. A TransactionInDoubtException akkor lesz eldobva, ha a tranzakciókezelő nem tud véglegesítési döntést hozni. A tranzakció véglegesítése esetén a rendszer nem ad kivételt.

Tranzakció visszagördülése

Ha vissza szeretne állítani egy tranzakciót, ne hívja meg a metódust Complete a tranzakció hatókörében. A hatókörön belül például kivételt is alkalmazhat. A tranzakció, amelyben részt vesz, vissza lesz állítva.

Tranzakciófolyamat kezelése a TransactionScopeOption használatával

A tranzakció hatókörének beágyazása lehetséges úgy, hogy egy olyan metódust hívunk meg, amely egy TransactionScope használó metódusból származik, amely a saját hatókörét alkalmazza, ahogyan az RootMethod a következő példában szereplő metódus esetében is előfordul.

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

A legfelső tranzakciós hatókört gyökérhatókörnek nevezzük.

Az TransactionScope osztály számos túlterhelt konstruktort biztosít, amelyek elfogadják a típus TransactionScopeOptionenumerálását, amely meghatározza a hatókör tranzakciós viselkedését.

Egy TransactionScope objektumnak három lehetősége van:

  • Csatlakozzon a környezeti tranzakcióhoz, vagy hozzon létre egy újat, ha nem létezik.

  • Legyen egy új gyökérhatókör, vagyis indítson el egy új tranzakciót, amely a saját hatókörén belül az új környezeti tranzakció lesz.

  • Egyáltalán nem vesz részt tranzakcióban. Ennek eredményeként nincs háttérben futó tranzakció.

Ha a Required hatókört példányosítják, és van egy aktuális tranzakció, a hatókör csatlakozik ehhez a tranzakcióhoz. Ha viszont nincs környezeti tranzakció, akkor a hatókör létrehoz egy új tranzakciót, és a fő hatókör lesz. Ez az alapértelmezett érték. Amikor Required kerül használatra, a hatókörben lévő kódnak nem kell másképp viselkednie, függetlenül attól, hogy a gyökér-e, vagy csak csatlakozik a környezeti tranzakcióba. Mindkét esetben azonos módon kell működnie.

Ha RequiresNew-val példányosítjuk a hatókört, akkor az mindig a gyökérhatókör. Új tranzakciót indít el, amely az adott hatókörön belül a környezeti tranzakcióvá válik.

Ha a hatókör Suppress-vel van példányosítva, az soha nem vesz részt tranzakcióban, függetlenül attól, hogy jelen van-e környezeti tranzakció. Az ezzel az értékkel példányosított hatókör mindig ambient tranzakcióként van null megadva.

A fenti lehetőségeket az alábbi táblázat foglalja össze.

TransactionScopeOption Környezeti tranzakció A hatókör részt vesz a
Kötelező Nem Új tranzakció (ez lesz a gyökér)
Újat igényel Nem Új tranzakció (ez lesz a gyökér)
Elnyom Nem Nincs tranzakció
Kötelező Igen Környezeti tranzakció
Újat igényel Igen Új tranzakció (ez lesz a gyökér)
Elnyom Igen Nincs tranzakció

Ha egy TransactionScope objektum csatlakozik egy meglévő környezeti tranzakcióhoz, előfordulhat, hogy a hatókör objektumának törlése nem fejezi be a tranzakciót, kivéve, ha a hatókör megszakítja a tranzakciót. Ha a környezeti tranzakciót egy gyökérhatókör hozta létre, csak akkor hívják meg a Commit a tranzakcióban, amikor a gyökérhatókört megsemmisítik. Ha a tranzakció manuálisan lett létrehozva, a tranzakció akkor fejeződik be, amikor vagy megszakítja, vagy véglegesíti a létrehozója.

Az alábbi példa egy TransactionScope olyan objektumot mutat be, amely három beágyazott hatókörobjektumot hoz létre, amelyek mindegyike más TransactionScopeOption értékkel van példányosítva.

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

A példa egy olyan kódblokkot mutat be, amely környezeti tranzakció nélkül hoz létre új hatókört (scope1) a következővel Required: . A hatókör scope1 egy gyökérhatókör, mivel létrehoz egy új tranzakciót (A tranzakció), és környezeti tranzakcióvá teszi a tranzakciót. Scope1 ezután további három objektumot hoz létre, mindegyik más TransactionScopeOption értékkel. Például a scope2 létrejön a Required segítségével, és mivel van egy környezeti tranzakció, csatlakozik a scope1 által létrehozott első tranzakcióhoz. Vegye figyelembe, hogy scope3 az új tranzakció gyökér hatóköre, és hogy scope4 nem rendelkezik környezeti tranzakcióval.

Bár az alapértelmezett és leggyakrabban használt érték TransactionScopeOption az Required, a többi értéknek egyedi rendeltetése van.

Tranzakciós hatókörön belüli nem tranzakciós kód

Suppress akkor hasznos, ha meg szeretné őrizni a kódszakasz által végrehajtott műveleteket, és nem szeretné megszakítani a környezeti tranzakciót, ha a műveletek sikertelenek. Ha például naplózási vagy auditálási műveleteket szeretne végrehajtani, vagy ha eseményeket szeretne közzétenni az előfizetők számára, függetlenül attól, hogy a környezeti tranzakció véglegesedik vagy megszakad. Ez az érték lehetővé teszi, hogy egy tranzakció hatókörén belül ne tranzakciós kódszakasz legyen, ahogyan az az alábbi példában is látható.

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
}

Szavazás beágyazott hatókörön belül

Bár a beágyazott hatókör csatlakozhat a gyökérhatókör környezeti tranzakciójához, a beágyazott hatókörben való hívásnak Complete nincs hatása a gyökérhatókörre. A tranzakció csak akkor lesz véglegesítve, ha a legfelső szintű hatókörtől az utolsó beágyazott hatókörig az összes hatókör a tranzakció véglegesítésére szavaz. A beágyazott hatókörben való hívás Complete nem befolyásolja a gyökér hatókört, mivel a környezeti tranzakció azonnal megszakad.

A TransactionScope időtúllépésének beállítása

A TransactionScope néhány túlparaméterezett konstruktorja elfogad egy TimeSpan típusú értéket, amely a tranzakció időtúllépésének szabályozására szolgál. A nullára állított időkorlát végtelen időkorlátot jelent. A végtelen időtúllépés többnyire hibakereséshez hasznos, ha a kódon keresztüli lépésekkel el szeretné különíteni az üzleti logikában lévő problémákat, és nem szeretné, hogy a hibakereső tranzakció időtúllépést okoz a probléma megkeresése során. Legyen rendkívül óvatos a végtelen időtúllépési érték használatával minden más esetben, mert felülírja a tranzakciós holtpontok elleni védelmet.

Általában két esetben állítja be az időtúllépést TransactionScope nem alapértelmezett értékre. Az első a fejlesztés során, amikor tesztelni szeretné, hogy az alkalmazás hogyan kezeli a megszakított tranzakciókat. Ha az időtúllépést egy kis értékre (például egy ezredmásodpercre) állítja, a tranzakció meghiúsul, így megfigyelheti a hibakezelési kódot. A második eset, amikor az értéket az alapértelmezett időtúllépésnél kisebb értékre állítja be, az az, amikor úgy véli, hogy a hatókör részt vesz az erőforrás-versengésben, ami holtpontot eredményez. Ebben az esetben a lehető leghamarabb megszakítja a tranzakciót, és nem várja meg, amíg az alapértelmezett időtúllépés lejár.

Ha egy hatókör csatlakozik egy környezeti tranzakcióhoz, de kisebb időtúllépést ad meg, mint amelyikre a környezeti tranzakció be van állítva, a rendszer kikényszeríti az új, rövidebb időtúllépést az TransactionScope objektumon, és a hatókörnek a megadott beágyazott időn belül véget kell vetnie, vagy a tranzakció automatikusan megszakad. Ha a beágyazott hatókör időtúllépése nagyobb, mint a környezeti tranzakcióé, annak nincs hatása.

A TransactionScope elkülönítési szintjének beállítása

Az TransactionScope túlterhelt konstruktorainak némelyike elfogad egy TransactionOptions típusú struktúrát, amely egy elkülönítési szintet határoz meg az időtúllépési érték mellett. Alapértelmezés szerint a tranzakció a következő elkülönítési szinttel Serializablefut: . Az olvasásigényes rendszerekhez gyakran egy a Serializable-től eltérő elkülönítési szintet választanak. Ez megköveteli a tranzakciófeldolgozás elméletének és magának a tranzakciónak a szemantikájának, az egyidejűségi problémáknak és a rendszerkonzisztenciának a következményeinek szilárd megértését.

Emellett nem minden erőforrás-kezelő támogatja az elkülönítés minden szintjét, és dönthetnek úgy, hogy a tranzakcióban a konfiguráltnál magasabb szinten vesznek részt.

Minden izolációs Serializable szint érzékeny az inkonzisztenciára, amely abból ered, hogy más tranzakciók hozzáférnek ugyanahhoz az információhoz. A különböző elkülönítési szintek közötti különbség az olvasási és írási zárolások használata. A zárolás csak akkor tartható meg, ha a tranzakció hozzáfér az erőforrás-kezelő adataihoz, vagy a tranzakció véglegesítése vagy megszakítása előtt tartható. Az előbbi jobb az átviteli sebességhez, az utóbbi a konzisztenciához. A két típusú zárolás és a két művelettípus (olvasási/írási) négy alapvető elkülönítési szintet ad. További információért lásd a IsolationLevel jelű részt.

Beágyazott TransactionScope objektumok használatakor minden beágyazott hatókört úgy kell konfigurálni, hogy pontosan ugyanazt az elkülönítési szintet használja, ha csatlakozni szeretnének a környezeti tranzakcióhoz. Ha egy beágyazott TransactionScope objektum megpróbál csatlakozni a környezeti tranzakcióhoz, de egy másik elkülönítési szintet határoz meg, a rendszer egy ArgumentException műveletet hajt ki.

Interoperabilitás COM+-szal

Amikor új TransactionScope példányt hoz létre, az egyik konstruktorban használhatja az EnterpriseServicesInteropOption felsorolást annak meghatározására, hogyan kíván interakcióba lépni a COM+-szal. Erről további információt az Enterprise Services és a COM+ Tranzakciók együttműködése című témakörben talál.

Lásd még