Poznáváme C# a Microsoft .NET 58. díl – Transakce v ADO .NET

Seznámení s transakcemi a hlavně s jejich použitím pomocí objektů patřících do světa ADO .NET je náplní tohoto dílu, který bude naposledy popisovat práci v takzvaném připojeném scénáři přístupu k datům.

Transakční zpracování

I když jsem v počátečním díle pojednávajícím o technologii ADO .NET poznamenal, že budu předpokládat základní znalosti jazyka SQL a databázových serverů, dovolím si pár slov o transakčním zpracování.

V transakčním zpracování je sekvence různých příkazů vykonána jako celek, jinými slovy jako jedna atomická operace. Této jednotce proveditelné práce říkáme transakce. To pro nás při vývoji aplikací znamená, že pokud jsou nějaké příkazy prováděny v transakci, musí být všechny úspěšně vykonány, aby byly jimi provedené změny promítnuty do databáze. V opačném případě, tedy například pokud vykonání kteréhokoli z příkazů v transakci skončí chybou nebo nevrátí očekávaný výsledek, nejsou změny prováděné předchozími příkazy do databáze vůbec promítnuty.

Takže transakční zpracování využijeme v případech, kdy potřebujeme, aby byl úspěšně vykonán každý z množiny námi definovaných příkazů zařizující jednu logickou operaci, nebo aby v případě, že jeden z těchto příkazů nelze korektně provést, nebyl vykonán žádný. Toho často využijeme v situacích, kdy modifikujeme data ve více než v jedné tabulce a je potřebné, aby všechny příkazy byly provedeny, jinak dojde k nekorektnosti dat. Dobrý příklad je realizace převodu mezi účty různých bank, kdy je potřeba v tabulce, která slouží k evidenci účtů klientů jedné banky provést příkaz na odečtení určité částky peněz a naopak v tabulce účtů druhé banky je potřeba peníze na účet přičíst.

Transakce, které tedy představující nejmenší jednotku proveditelné práce v transakčním zpracování, musí ze své definice splňovat čtyři vlastnosti, který jsou atomicita (transakce je nejmenší jednotka práce, buď jsou provedeny všechny příkazy nebo žádný), konzistence (po provedení transakce musí jsou data v datovém zdroji v konzistentním stavu), izolace (pokud je prováděno více transakcí najednou musí být jimi prováděné změny od sebe navzájem určitým způsobem izolovány – například jedna transakce vůbec nemusí vidět na změny prováděné jinou transakcí) a trvanlivost (pokud je transakce ukončena jsou změny jí provedené již trvale promítnuty do datového zdroje).

Využití transakčního zpracování v ADO .NET

Jelikož je komponenta ADO .NET určena pro komplexní práci s daty, tak je samozřejmě možné pomocí ní pracovat s transakcemi. Stejně jako v případě spojení nebo příkazu (IDbConnection, IDbCommand) je i pro transakce v ADO .NET rozhraní IDbTransaction, které je na úrovní jednotlivých ADO .NET data providerů implementováno odpovídající třídou (SqlTransaction, OracleTransaction…). Nejdůležitější metody tohoto typu, kterými jsou metody Commit a Rollback, jsou použity v následujícím příkladu.

/// <summary>
/// Priklad na pouziti transakci
/// </summary>
internal class TransactionExam
{
  static internal void Run()
  {
    //vytvoreni spojeni
SqlConnection lConn = new SqlConnection(@"...");
    SqlCommand lComm1 = lConn.CreateCommand();
    SqlCommand lComm2 = lConn.CreateCommand();
    //prikaz na smazani obsahu tabulky
    lComm1.CommandText = "delete from employee";
    //prikaz na vytvoreni zaznamu
    lComm2.CommandText = "insert into employee(FirstName, SurName, BirthDate) values(@FirstName, @SurName, @BirthDate)";
    //vytvoreni parametru pro prikaz, ktery vytvari zaznamy
    SqlParameter lFNamePar = new SqlParameter("@FirstName", SqlDbType.NVarChar);
    SqlParameter lSNamePar = new SqlParameter("@SurName", SqlDbType.NVarChar);
    SqlParameter lBDatePar = new SqlParameter("@BirthDate", SqlDbType.DateTime);

    //vytvoreni hodnot parametru
    lComm2.Parameters.Add(lFNamePar).Value = "Jan";
    lComm2.Parameters.Add(lSNamePar).Value = "Novak";
    lComm2.Parameters.Add(lBDatePar).Value = DateTime.Now;

    SqlTransaction lTrans = null;
    try
    {
      lConn.Open();
      //zahajeni transakce
      lTrans = lConn.BeginTransaction();
      //oba prikazy budou patrit do jedne transakce
      lComm1.Transaction = lTrans;
      lComm2.Transaction = lTrans;
      //vykonani obou prikazu
      lComm1.ExecuteNonQuery();
      lComm2.ExecuteNonQuery();
      //potvrzeni transakce
      lTrans.Commit();
      Console.WriteLine("Oba prikazy byly uspesne vykonany");
    }
    catch(SqlException ex)
    {
      //v pripade, ze doslo k chybe vratime transakci
      if (lTrans != null)
      {
        try
        {
          lTrans.Rollback();
        }
        catch(Exception ex2)
        {
          Console.WriteLine("Pri rollbacku transakce doslo k chybe : {0}", ex2);
        }
      }
      Console.WriteLine(ex);
    }
    finally
    {
      if (lConn != null)
      {
        lConn.Close();
      }
    }
  }
}

V příkladu jsou vytvořeny dvě instance třídy SqlCommand, představující příkazy, které budou provedeny v transakci. První z nich zařídí, že bude smazán celý obsah tabulky a druhý následně do této prázdné tabulky přidá jeden záznam o zaměstnanci. Objekt představující transakci je pro databázi MS SQL server instancí třídy SqlTransaction. Instanci této třídy získáme pomocí metody BeginTransaction na instanci třídy představující spojení k databázi (implementace rozhraní IDbConnection). Tím sdělíme databázi, že pod tímto spojením chceme započít novou transakci.

K tomu, aby byly oba námi vytvořené příkazy součástí započaté transakce a tím pádem byly oba dva provedeny jako jedna atomická operace, využijeme vlastnosti definované na rozhraní IDbCommand (a tedy i na námi používaném typu SqlCommand), kterou je vlastnost Transaction. Po tomto kroku již provedeme oba příkazy pomocí nám již známe metody ExecuteNonQuery. Pokud jsou oba příkazy úspěšně vykonány, tak chceme transakci ukončit a výsledky operací provedených našimi dvěma dotazy odevzdat a tím je zviditelnit i mimo transakci. To provedeme zavoláním metody Commit na instanci typu implementujícího rozhraní IDbTransaction (v našem případě tedy SqlTransaction).

V případě, že během vykonávání jednoho z vytvořených příkazů dojde k chybě a je vyhozena výjimka, tak je v bloku catch tato výjimka zachycena a jako reakce na vzniklou chybu je stav databáze navrácen do původního stavu před započetím provádění transakce. Tohoto je dosaženo zavoláním metody Rollback na objektu transakce. Pokud bychom tuto metodu nezavolali, tak by byli v databázi změny čekající na potvrzení (metodou Commit). Důležité je ovšem poznamenat, že tyto změny by byli čekající pouze po dobu existence objektu reprezentujícího transakci, protože předtím než je tento objekt zrušen, je na něm zavolána metoda Rollback, samozřejmě pouze v případě, že na něm v minulosti nebyla zavolána metoda Commit nebo Rollback.

Poznámka: Schválně si vyzkoušejte upravit text druhého příkazu, který do tabulky přidává nový záznam, do takové podoby, aby při jeho vykonání vznikla chyba a uvidíte, že se s obsahem tabulky nic nestane a to i přestože byl již vykonán první příkaz, který by měl všechna data z tabulky smazat.

Pokud jsou změny provedené příkazy v transakci již jednou odevzdány metodou Commit, není již možné jednoduše pomocí metody Rollback vrátit databázi do stavu, ve kterém byla před již ukončenou transakcí. Takže pokud bychom se pokoušeli spustit následující kód, byla by vyhozena výjimka typu InvalidOperationException.

/// <summary>
/// Chybny priklad na pouziti transakci
/// </summary>
internal static void RollbackAfterCommit()
{
  SqlConnection lConn = new SqlConnection(@"...");
  SqlCommand lComm = lConn.CreateCommand();
  lComm.CommandText = "delete from employee where SurName=`Pus`";
  SqlTransaction lTrans = null;
  try
  {
    lConn.Open();
    lTrans = lConn.BeginTransaction();
    lComm.Transaction = lTrans;
    lComm.ExecuteNonQuery();
    //ukonceni - potvrzeni transakce
lTrans.Commit();

    //..dalsi kod

    //bude vyhozena vyjimka, protoze transakce je jiz ukoncena
    lTrans.Rollback();
    }
    finally
    {
      if (lConn != null)
      {
        lConn.Close();
      }
    }
  }

Stejně tak by byla tato výjimka vyhozena v opačném případě, tedy když byla již zavolána metoda Rollback a po té se pokusíme zavolat metodu Commit.

Příklady k tomuto dílu jsou ke stažení zde.

Témata článku: Software, Microsoft, Programování, Catch

3 komentáře

Nejnovější komentáře

  • Pavel Polívka 24. 11. 2007 13:50:28
    Programovou oflline verzi seriálu naleznete ke stažení na...
  • Mem_, Mem_ 20. 1. 2006 16:02:45
    Jak to funguje "fyzicky" vam nepovim (snad se objevi nekdo povolanejsi), v...
  • Analphabethdt 20. 1. 2006 13:46:11
    Co se ASP.NET tyce, jsem ponekud analfabet. Muze mi nekdo vysletlit, jak...
Určitě si přečtěte

Sbíječky vyměnili za klávesnice. Nový projekt má za cíl přeučit horníky na programátory

Sbíječky vyměnili za klávesnice. Nový projekt má za cíl přeučit horníky na programátory

** Programátorů je málo a horníků bez práce po uzavření dolu Paskov bude moc ** Problém řeší unikátní projekt ** Pilotní kurz dává naději, že by z horníků mohli být použitelní kodéři

28.  11.  2016 | David Polesný | 79

ASUS ZenBook 3 se začal prodávat v Česku. Je ve všem lepší než MacBook, ale bude to stačit?

ASUS ZenBook 3 se začal prodávat v Česku. Je ve všem lepší než MacBook, ale bude to stačit?

** Novinka od Asusu míří přímo proti MacBooku od Applu ** Nabídne daleko více výkonu za stejné peníze

2.  12.  2016 | David Polesný | 126