Poslední dva dny jsem se vztekal při hledání důvodu problémů, které nastávaly při použití technologie pro řízení transakcí (System.Transactions) v .NET Frameworku. Většinou o ní slýcháváme díky třídě TransactionScope, která zastupuje blok kódu, jehož běh má být transakční.
K čemu slouží TransactionScope?
Řízení transakcí je implementováno v řadě technologií obsažených v .NET Frameworku – příkladem může být WCF nebo klient pro komunikaci s databází. Zároveň je dobré zmínit, že pokud komponenta neimplementuje komunikaci s transakčním jádrem, není kód transakcí nijak ovlivněn. Jako asi největší výhodu využití vidím v distribuovaných systémech. Transakci je totiž možné propagovat i skrze komunikační kanál WCF.
Pokud se transakce propaguje skrz služby mezi více počítačů, je nutné mít na obou počítačích aktivní MSDTC službu s povoleným vzdáleným přístupem a správně nastavený firewall. Důležitý je fakt, že při samotné komunikaci zahajuje volání počítač, který jako první vyžaduje od toho druhého nějaké informace o transakci. Oba stroje tedy musí mít možnost přímého spojení pomocí NETBIOS jména (doménové jméno nefunguje). Pro zjištění, zda je vše správně nastaveno lze využít jednoduchou aplikaci Microsoft DTC Ping.
Více spojení k databázi v jednom TransactionScope
Jak jsem již psal na začátku, objevil se při použití TransactionScope problém. Z nějakého tehdy neznámého důvodu aplikace hlásila, že nelze vytvořit distribuovanou transakci. Konkrétně:
Communication with the underlying transaction manager has failed.
[INNER EXCEPTION] The MSDTC transaction manager was unable to pull the transaction
from the source transaction manager due to communication problems. Possible causes
are: a firewall is present and it doesn't have an exception for the MSDTC process,
the two machines cannot find each other by their NetBIOS names, or the support
for network transactions is not enabled for one of the two transaction managers.
(Exception from HRESULT: 0x8004D02B)
Přesně tuto chybu obdržíte, pokud se pokoušíte vytvářet distribuovanou transakci mezi více počítači a jeden z nich není možné zkontaktovat. V této konkrétní aplikace se však transakce po síti na jiný počítač distribuovat nemusela. Vždy se totiž napřímo volala komunikace s databázi a transakce se z toho důvodu měla předávat mezi jednotlivými lokálními SqlClienty. Konkrétně uvnitř metody SqlConnection.Open() se konktroluje, zda se neprovádí kód uvnitř bloku s aktivní transakcí a pokud ano, transakce se převezme a použije pro komunikaci s databází.
Celý problém tkvěl v naprosto banální záležitosti. Uvnitř transakčního bloku se v jedné metodě vnitřně využívalo další spojení k databázi. Toto spojení se neuzavíralo ihned (čekalo se na dokončení celého bloku) a nebylo tak možné mezitím vytvořit jakékoliv nové spojení. Důvodem je právě ona transakce, která může být aktivní v jednu chvíli vždy jen u jednoho připojení (fyzicky nelze mít otevřených více připojení k SQL Serveru, které sdílejí jednu transakci). Toto řeší velmi elegantně například Entity Framework, který po sobě spojení ihned uzavírá a může tak bez problémů existovat více datových kontextů uvnitř jedné transakce.
Příklad kódu, který vyvolá chybu:
Otázkou zůstalo, proč .NET Framework hlásil chybu, že nelze vytvořit distribuovanou transakci. Odpověď jsem nalezl při bližším zkoumání zdrojových kódů tříd SqlConnection. Platí totiž pravidlo, že pokud je aktivní transakce, pokusí se ji zajistit pro aktuálně otevírané spojení z aktuální aplikace. Pokud to možné není, transakci se pokusí získat skrze takzvaný Distributed Transaction Coordinator (DTC nebo také Microsoft DTC, či MSDTC) přímo od SQL Serveru. Vzhledem k situaci, kdy je aktuální transakce aplikována na otevřené spojení, není ji fyzicky možné aplikovat na spojení druhé, jenž se právě otevírá. Proto se kontaktuje MSDTC služba a snaží se transakci získat – to se však pochopitelně nepovede, pokud nejsou dodrženy všechny pravidla pro uskutečnění distribuovaných transakcí a kód končí nesrozumitelnou, výše uvedenou chybou.
Závěrem
Více instancí SqlClient třídy, či Entity Framework kontextů, je možné využívat v rámci jednoho bloku TransactionScope. Je však důležité zaručit, že je naráz otevřeno vždy jen jedno spojení. Tím dáváme možnost, aby se transakce správně předávala a byla přiřazena vždy jen maximálně k jednomu připojení.
Závěrem – updated
Více instancí SqlClient třídy, či Entity Framework kontextů, je možné využívat v rámci jednoho bloku TransactionScope. Pokud jich však chceme mít otevřených více najednou, je nutné, aby byla spuštěna MSDTC služba a pokud je navíc SQL Server umístěn jinde, musí oba tyto počítače splňovat pravidla pro uskutečnění distribuovaných transakcí:
- MSDTC služba je spuštěna na obou počítačích a nakonfigurována pro přístup ze sítě.
- Firewall je povolený a počítače mohou spolu komunikovat.
- Počítače se vidí skrze svá NetBIOS jména.
Pokud jsou tyto body dodrženy, počítač naváže distribuovanou transakci s SQL Serverem serverem a je možné transakci sdílet mezi více připojeními.