TransactionScope je nový objekt pro správu transakcí v ADO.NET 2. Podívejme se, co nám nabízí a jak nám můze usnadnit práci.
reklama
Transakce, základní to vlastnost databází. Díky základním vlastnostem transakcí: atomičnosti, konzistenci, izolovanosti a trvanlivosti (tzv. ACID) můžeme provádět mnohé operace, které by jinak byly velmi obtížné.
Dovolím si malé odbočení a vysvětlím, co jednotlivé kousky znamenají. Atomičnost znamená, že daný blok kódu je proveden buď celý, nebo je nastaven stav, jako by nebyl proveden vůbec. Konzistence nám zajišťuje, že po ukončení transakce vyhovují všechna data integritním omezením, jsou tedy tzv. konzistentní. Předposlední pojem – izolovanost – se odvolává na schopnost provádět operace v transakci bez ohledu na to, co dělají jiné transakce. Transakce svoje změny, během vykonávání, navzájem nevidí. A nakonec trvanlivost, která určuje, že změny provedené v potvrzené transakci se nemůžou nikdy ztratit.
Toliko malá odbočka. Podíváme-li se na podporu transakcí (pro databáze) v .NET Frameworku resp. v ADO.NET nalezneme klasickou implementaci pomocí objektu typu „transakce“. V ADO.NET by měl každý takovýto objekt implementovat interface IDbTransaction a tedy implementovat základní množinu metod a vlastností. Práce s tímto objektem je velmi přímočará.
using (IDbConnection conn = new SqlConnection("<some connection string>"))
{
using (IDbTransaction trans = conn.BeginTransaction())
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "<some command>";
cmd.Transaction = trans;
//cmd.Parameters.Add(...);
object result = cmd.ExecuteScalar();
}
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "<some command>";
cmd.Transaction = trans;
//cmd.Parameters.Add(...);
object result = cmd.ExecuteScalar();
}
trans.Commit();
}
}
Výše uvedený příklad nám ukazuje provedení dvou příkazů v databázi v jedné transakci a její potvrzení. Pokud bychom se rozhodli transakci zamítnout (tzv. rollback), změny provedené jak prvním, tak druhým příkazem (sql příkazem) budou zahozeny. Velmi jednoduché, velmi užitečné. Nicméně tento přístup má i své zápory. Předně pokud používáte nějakou mezivrstvu, nevíte, jak ona si transakce řídí a také nemáte možnost ovlivnit, co bude v jaké transakci (případně co s čím). Řešení na tento problém je možné vymyslet nespočet. My si ukážeme daný problém (a několik) řešení na klasické mezivrstvě a sice DataSetu resp. TableAdapteru.
Pokud používáte TableAdaptery pro plnění DataSetu čí promítnutí změn zpět do databáze, nemáte přímo k objektům transakce přístup. Jejich řízení se tak stává obtížnější, nikoli však nemožné. První, velmi přímočarou možností, je zpřístupnění si db objektů prostým přepsáním přístupových modifikátorů ve vygenerovaném zdrojáku. Jak jistě každý domyslí, vše bude v pořádku, než bude třída znovu vygenerována Visual Studiem (stačí libovolná změna v designeru). Jako další můžeme využít partial třídy, která nám taktéž může udělat vrátka, např. přes property, do TableAdapteru a díky oddělení od generovaného kódu nedojde k jejímu nechtěnému přepsání. Použitelné, nikoli ale rychlé a pěkné. Poslední možností, je samozřejmě využít reflection. Ačkoli je možné si najít svoje oblíbené řešení, ani jedno pravděpodobně nebude moc „handy“.
V ADO.NET 2 máme nový objekt TransactionScope z namespacu System.Transactions. Tento objekt nám všechny výše uvedené problémy s transakcemi hravě vyřeší. Využívání tohoto objektu (někdy je možné se setkat s označením enlisting, podle parametru v connection stringu) musí být však podporováno providerem pro databázi. Dobrou zprávou však je, že většina udržovaných providerů enlisting podporuje, alespoň v základním provedení (viz dále). Provider pro SQL Server jej samozřejmě podporuje (a např. provider pro Firebird také).
Vezmeme si první příklad a přepíšeme jej s použitím TransactionScope.
using (TransactionScope ts = new TransactionScope())
{
using (IDbConnection conn = new SqlConnection("<some connection string with enlisting>"))
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "<some command>";
//cmd.Parameters.Add(...);
object result = cmd.ExecuteScalar();
}
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "<some command>";
//cmd.Parameters.Add(...);
object result = cmd.ExecuteScalar();
}
ts.Complete();
}
}
Na první pohled se pro nás nic nezměnilo. Celý blok je obalen také, jen malinko jinak a jiným objektem. Na druhý pohled je to ale mnohem zajímavější. Pozorné oko si všimne, že spojení mezi TransactionScope není nikde definováno. A to je ono! Místo toho, abychom definovali transakci pro dané spojení, ve které budou provedeny příkazy, je nyní vše necháno na spojení. Spojení samo musí detekovat, zdali je v bloku TransactionScope (a je-li enlisting povolen) a podle toho se zachovat; nastartovat transakci a připravit si provedení příkazů v ní. Nakonec je možné zavolat metodu Complete, jako obdobu commitu. Pro rollback není žádná metoda, rollback je proveden automaticky, pokud není metoda Complete zavolána. Tato jednoduchá změna nám dává krásnou možnost, jak pracovat jednoduše s transakcemi při používání DataSetu apod. mezivrstev. Update dvou tabulek v DataSetu pomocí DataAdapteru je pak nadmíru přímočarý.
using (TransactionScope ts = new TransactionScope())
{
this.bARTableAdapter.Update(this.dsDemo.BAR);
this.fOOTableAdapter.Update(this.dsDemo.FOO);
ts.Complete();
}
Na výše uvedeném příkladu je krásně vidět síla této konstrukce. A není třeba se omezovat jen na tento jednoduchý případ. Obalit je možné jakékoli spojení a zakomponovat do transakce mnoho příkazů. Můžete tak metody, které nebyly od počátku navrženy pro práci v transakci jednoduše do transakce dostat.
Samo o sobě je toto velké usnadnění práce. Nicméně aby toho nebylo málo, je možné fungování ještě rozšířit. Do TransactionScope je možné zahrnout spojení na různé databáze (a využít něco na způsob klasického přístupu Two-Phase Commit). Stejně tak je možné využívat transakce přes více aplikačních domén či procesů. Pro více informací vyhledejte pojmy Microsoft Distributed Transaction Coordinator (MSDTC) a Promotable Transaction, jejich vysvětlení překračuje rámec tohoto článku. Bloky TransactionScope je možné do sebe dokonce vnořovat (i když je ale pak obtížnější správně pohlídat co kde člověk má).
Pokud by se někomu mohlo zdát, že možnosti a struktura programování databázových aplikací jsou ustáleny, není tomu tak. V základních věcech se samozřejmě nic nemění a struktura i návrh je velmi podobný ve všech jazycích. Ale nadstavby nad těmito základy se rozvíjejí, sice potichu, ale rozvíjejí. Kdo ví, možná se časem dočkáme, že db objekty budou umět sami poznat, co chceme v jaké transakci provádět. :)