Kdo byl při čtení minulého dílu pozorný, jistě si všiml, že konstruktor objektu TransactionScope, který jsme si popsali, není jediný v nabídce. A protože jsme si již minule řekli, že se nespokojíme s kdejakým implicitním chováním, ale chceme mít vše „pod palcem“, je jasné, že se dnes musíme podívat na zbytek resp. alespoň na tu zajímavější část.
reklama
Kdo byl při čtení minulého dílu pozorný, jistě si všiml, že konstruktor objektu TransactionScope, který jsme si popsali, není jediný v nabídce. A protože jsme si již minule řekli, že se nespokojíme s kdejakým implicitním chováním, ale chceme mít vše „pod palcem“, je jasné, že se dnes musíme podívat na zbytek resp. alespoň na tu zajímavější část.
První verze konstruktoru, která pravděpodobně upoutá náš zrak, je:
public TransactionScope (
Transaction transactionToUse,
TimeSpan scopeTimeout
)
Začnu poněkud netradičně, od konce, s parametrem scopeTimeout. Tento parametr určuje, na jak dlouho může blok „zatuhnout“, tedy čekat na např. na uvolnění jistého zdroje v databázi. Za normálních podmínek, kdy transakce v databázi sviští jedna za druhou, se není nutné o tento parametr téměř vůbec starat. Při správném přístupu by nemělo k dlouhému uváznutí docházet. Nicméně vyskytují se situace, kdy je třeba udělat náročnou operaci a ještě k tomu ve velmi restriktivní izolaci (TIL – Transaction Isolation Level, bude vysvětleno záhy). Typickým příkladem je přecenění skladu, výpočet výplat apod. Potom může aplikace buď čekat dokud není operace dokončena, což může být přinejmenším trochu matoucí pro uživatele nebo je možné čekat pouze stanovený časový interval a poté je transakce abortována. Obvyklé je pak dát možnost uživateli provést danou operaci – např. uložení dat – znova.
Druhý parametr, vlastně první v pořadí – transactionToUse – je typu Transaction. Ačkoli by se mohlo zdát, že je možné předat objekt typu SqlTransaction či lépe IDbTransaction není tomu tak. Jedná se v plném zápisu o objekt typu System.Transactions.Transaction, čili ze stejného „balíku“ jako TransactionScope. Vlastně jde o možnost předání transakce, která se stane nadřazenou pro tento blok. Podobně jako když bloky do sebe vnořujeme. Vlastní příkazy uvnitř pak tuto transakci používají.
Druhá zajímavá volba je:
public TransactionScope (
TransactionScopeOption scopeOption,
TransactionOptions transactionOptions
)
První parametr scopeOptions jsme již poznali v minulém dílu, nebudu se jím proto zabývat a případné zájemce odkazuji na předchozí díl. Druhý parametr transactionOptions, který je instancí struktury TransactionOptions (pozor na TransactionScopeOptions vs. TransactionOptions), je o mnoho zajímavější. Struktura, kromě několika metod zděděných z vyšších vrstev, obsahuje dvě zajímavé property. IsolationLevel a Timeout. Vlastnost Timeout nám zajistí to samé, co parametr scopeTimeout u předchozího provedení. Naproti tomu IsolationLevel nám nabídne něco nového a mnohem zajímavějšího. O několik řádků výše jsem mluvil o tzv. izolaci transakce (TIL). A právě tato property nám umožňuje ji nastavit. Kupříkladu IsolationLevel.ReadCommited, IsolationLevel.Serializable či IsolationLevel.Snapshot – pokaždé se bude naše transakce chovat trošinku jinak, resp. bude jinak interagovat s jinými transakcemi, které v tu chvíli nad databází probíhají.
Protože to není náplní článku, zmíním se jen lehce o tom, co to izolace transakcí jsou. Jak jsem již řekl, nastavením různých TIL můžete ovlivňovat, jak transakce „spolupracuje“ s ostatními. Základní snahou databázového stroje je provádět příkazy transakcí tak, jako by byly prováděny „za sebou“ resp. aby tak dopadl výsledek a na druhou stranu provádět transakce souběžně jak je to jen možné. Vidíme, že požadavky jdou proti sobě. A jelikož není možné najít optimální kompromis pro každého, vznikly úrovně izolace transakcí (Transaction Isolation Levels). Nutno poznamenat, že implementace v jednotlivých DB se může mírně lišit, nicméně základy zůstávají. ReadCommited např. čte vždy poslední potvrzená data, což může být vhodné pro většinu případů, ale pro účetní uzávěrku určitě nikoli, neboť mohou vznikat fantomové řádky apod. Naproti tomu Serializable izolace zaručuje reprodukovatelné čtení, žádné fantomové řádky a možnost změny přečtených dat – téměř jako by byly transakce prováděny postupně. Přesný popis chování včetně podporovaných úrovní určitě naleznete v dokumentaci ke své oblíbené databázi.
Toliko malé odbočení. Vrátíme-li se zpět k našemu konstruktoru a možnosti specifikovat TIL pro TransactionScope blok, dostáváme do ruky velmi silný nástroj s možnostmi srovnatelnými s klasickým objektem používaným pro transakce implementujícím IDbTransaction. A ve velké množině případů nám tento konstrukt může ušetřit mnoho práce.
Pokud tedy nepotřebuji opravdu speciální chování, které by bylo stejně lepší zapouzdřit do vlastních objektů, není TransactionScope blok žádnou „brzdou“. A díky možnosti použít jej v obecnějších případech přispívá nejen k čitelnosti kódu.