Poznáváme C# a Microsoft .NET 59. díl – transakce v ADO .NET II.

Po seznámení se s transakcemi a jejich základním použitím prostřednictvím ADO .NET si dnes naše vědomosti o použití transakčního zpracování rozšíříme o znalosti, které nám umožní ukládat stav transakce a nebo definovat úroveň izolace jednotlivých transakcí.

Uložení stavu transakce

Zatím jsme trasnakční zpracování poznali jen v té podobě, ve které byla transakce prováděna a v případě, že se vyskytl nějaký problém, vrátili jsme databázi do stavu před započetím transakce pomocí metody Rollback typu IDbTransaction. Nicméně v některých situacích budeme chtít v průběhu transakce vytvořit body, ke kterým se lze v případě neúspěchu některé z následujících operací vrátit a potvrdit změny provedené do doby vytvoření zmíněného bodu. Tyto body jsou nazývány Savepoints a je možné je použivat na většině moderních databázových systémů. Pomocí Savepoints tedy dosáhneme toho, že můžeme pod určitým jménem uložit stav transakce, ke kterému se lze později vrátit.

K využití této vlastnosti pomocí ADO .NET využijeme metodu Save typu IDbTransaction v kombinaci s určitou přetíženou verzí nám již známé metody Rollback. Na ukázku použití je tu následující příklad.

/// <summary>
/// Priklad na ulozeni stavu transakce
/// </summary>
internal class SavePointExam
{
  internal static void Run()
  {
    SqlConnection lConn = new SqlConnection(@"...");
    SqlCommand lComm1 = lConn.CreateCommand();
    SqlCommand lComm2 = lConn.CreateCommand();
    //vytvoreni prikazu pro vlozeni zaznamu do databaze
    lComm1.CommandText = "insert into employee (FirstName, SurName, BirthDate) values (`Prvni`, `Zaznam`, @BirthDate)";
    lComm2.CommandText = "insert into employee (FirstName, SurName, BirthDate) values (`Druhy`, `Zaznam`, @BirthDate)";

    lComm1.Parameters.Add("@BirthDate", SqlDbType.DateTime).Value = DateTime.Now;
    lComm2.Parameters.Add("@BirthDate", SqlDbType.DateTime).Value = DateTime.Now;

    SqlTransaction lTrans = null;
    try
    {
      lConn.Open();
      //zahajeni transakce
      lTrans = lConn.BeginTransaction();
      lComm1.Transaction = lTrans;
      lComm2.Transaction = lTrans;
      lComm1.ExecuteNonQuery();
      //vytvoreni savepointu
      lTrans.Save("point");
      lComm2.ExecuteNonQuery();
      //navraceni stavu databaze do stavu v jakem byla pri
      //vytvoreni savepointu
      lTrans.Rollback("point");
      //potvrzeni transakce - databazi ovlivni pouze prvni dotaz
      lTrans.Commit();
      Console.WriteLine("Transakce byla ukoncena");
    }
    catch(Exception ex)
    {
      if (lTrans != null)
      {
        try
        {
          lTrans.Rollback();
        }
        catch(Exception ex2)
        {
          Console.WriteLine("Chyba pri rollbacku : {0}", ex2);
        }
      }
      Console.WriteLine(ex);
    }
    finally
    {
      if (lConn != null)
      {
        lConn.Close();
      }
    }
  }
}

V příkladu jsou vytvořeny dva příkazy, které přidávájí záznam do databázové tabulky. Oba tyto příkazy jsou vykonány v rámci jedné transakce. Po vykonání prvního příkazu je pomocí metody Save vytvořen savepoint se specifikovaným jménem. Od této chvíle je možné se kdykoli v průběhu transakce vrátit do stavu, kdy byl proveden pouze první příkaz. A samozřejmě je této možnosti v našem příkladu využito. A to tak, že po té co je vykonán druhý příkaz v transakci je transakce vrácena do stavu, ve kterém byla při vytvoření našeho savepointu.

Toho docílíme zavoláním metody Rollback, avšak v jiné přetížené verzi, než jsme doposud používali a to ve verzi, která jako svůj parametr přijímá objekt typu String, který specifikuje do jakého stavu (k jakému savepointu) má být transakce vrácena. Po návracení běžící transakce do stavu, ve kterém byla při vytváření savepointu, je již transakce potrzena metodou Commit.

A jaký je výsledek? Stačí se po spuštění tohoto příkladu podívat do databázové tabulky s níž příklad pracoval a uvidíme, že i když byli v transakci provedny dva příkazy přidávájící záznamy a transakce byla potrzena, byl do tabulky přidán pouze záznam, který vytvořil první příkaz. A to právě z důvodu vrácení transakce do specifického stavu, kdy jsou všechny vykonané příkazy po vytvoření savepointu vráceny zpět a při potrvzení transakce jsou do databáze promítnuty pouze ty příkazy, které byly v rámci transakce vykonány do vytvoření savepointu.

Úroveň izolace transakcí

Když jsem v minulém díle zmiňoval čtyří základní vlastnosti transakci jednou z nich byla vlastnosti s názvem izolace. Izolace je velmi důležitou vlastností, protože se nezřídka stává, že v jednou chvíli běží více transakci na různých spojeních a úroveň izolace těchto transakcí určuje, jaká data může daná transakce “vidět”.

Úroveň izolace je na objektu transakce v ADO .NET určována vlastností IsolationLevel, která nabývá hodnotu z výčtu IsolationLevel. Jako výchozí úroveň izolace, který je použit pří spouštění transakce (IDbConnection.BeginTransaction) je představován hodnotout ReadCommited. Tato úroveň izolaci určuje, že daná transace může číst pouze data, která byla již jinými transakcemi odevzdána, což je také nejpoužívanější a pro většinu transakcí doporučená úroveň. Ovšem mohou nastat situace, kdy ani tato úroveň izolace není potřebná a v tom případě využijeme možnosti úroveň izolace určit.

V následujícím příkladu je jedné z transakcí nastavena nižší úroveň izolace a demonstruje jaké to má následky.

/// <summary>
/// Ukazka pouziti izolacnich urovni transakci
/// </summary>
public class IsolationLevelExam
{
  static internal void Run()
  {
    //vytvoreni spojeni pro transakce
    SqlConnection lConn1 = new SqlConnection(@"...");
    SqlConnection lConn2 = new SqlConnection(@"...");
    SqlCommand lInsertComm = lConn1.CreateCommand();
    SqlCommand lSelectComm = lConn2.CreateCommand();
    SqlCommand lDeleteComm = lConn1.CreateCommand();
    //prikaz pro vytvoreni zaznamu
    lInsertComm.CommandText = "insert into employee (FirstName, SurName, BirthDate) values(`Jan`, `Novak`, @BirthDate)";
    //prikaz pro vybrani zaznamu
    lSelectComm.CommandText = "select * from employee where SurName=`Novak`";
    //prikaz pro odstraneni zaznamu
    lDeleteComm.CommandText = "delete from employee where SurName=`Novak`";

    lInsertComm.Parameters.Add("@BirthDate", SqlDbType.DateTime).Value = DateTime.Now;

    SqlTransaction lTrans1 = null;
    SqlTransaction lTrans2 = null;
    try
    {
      //otevreni obou spojeni
      lConn1.Open();
      lConn2.Open();

      //zapoceti obou transakci
      lTrans1 = lConn1.BeginTransaction();
      //prikazy v teto transakci mohou cist i neodevzdana data jinymi transakcemi
      lTrans2 = lConn2.BeginTransaction(IsolationLevel.ReadUncommitted);

      lInsertComm.Transaction = lTrans1;
      lSelectComm.Transaction = lTrans2;
      lDeleteComm.Transaction = lTrans1;
       
      lInsertComm.ExecuteNonQuery();
      Console.WriteLine("Transakce 1 vlozila zaznam");

      //bude zobrazen i zaznam pridany druhou transakci, prestoze
      //nebyla transakce potvrzena
      Console.WriteLine("Transakce 2 ziskala tato data :");
      ShowResult(lSelectComm.ExecuteReader());
       
      lDeleteComm.ExecuteNonQuery();
      Console.WriteLine("Transakce 1 odstranila zaznam");
       
      lTrans1.Commit();
    }
    catch(Exception ex)
    {
      Console.WriteLine(ex);
      if (lTrans1 != null)
      {
        lTrans1.Rollback();
      }
    }
    finally
    {
      if (lConn1 != null)
      {
        lConn1.Close();
      }
      if (lConn2 != null)
      {
        lConn2.Close();
      }
    }
   
  }

  /// <summary>
  /// Vypise vysledkovou sadu
  /// </summary>
  /// <param name="reader">reader vysledkove sady</param>
  static void ShowResult(IDataReader reader)
  {
    try
    {
      while (reader.Read())
      {
        for (int i = 0; i < reader.FieldCount; i++)
        {
          Console.Write("{0} - ", reader[i]);
        }
        Console.WriteLine();
      }
    }
    catch(Exception ex)
    {
      Console.WriteLine(ex);
    }
    finally
    {
      if (reader != null)
      {
        reader.Close();
      }
    }
  }
}

Pojďme se nyní podívat co se uvedný příklad snaží ukázat. V příkladu je simulován běh dvou konkurenčních transakcí. Každá transakce je prováděna na vlastním spojením, protože paraelní transakce nejsou na spojení k SQL Serveru podporovány. Všimněte si, že při spouštění druhé transakce je využita přetížená verze metody BeginTransaction přijímající parametr typu IsolationLevel, který nám umožňuje určit s jakou úrovní izolace bude transakce pracovat. Druhá transakce tedy bude pracovat s úrovní ReadUncommited, což znamená, že příkazy prováděné v této transakci mohou číst i záznamy, které nebyly jinými transakce ještě potvrzeny.

Takže když je první transakci přidán do tabulky záznam a příkaz běžící v druhé transakci vykoná dotaz, který ma vybrat všechny záznamy s určenou podmínku, tak je vybrán i záznam přidaný první transakcí (protože splňuje dané podmínky) a to i přes fakt, že první transakce zatím své změny nepotvrdila.

Úroveň izolace dané transakce není možné pouze snížit jak bylo ukázáno v předchozím příkladu, ale může být také zvýšena a to například na nejvyšší úroveň izolace, která je ve výčtu IsolationLevel představována hodnotou Serializable. Při použití této úrovně nejsou transakce spouštěny soubežně, ale jedna transakce je vždy ukončena a až po té je spuštěna transakce další.

Příklady vztahující se k tomuto dílu naleznete zde.

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

3 komentáře

Nejnovější komentáře

  • Pavel Polívka 24. 11. 2007 13:51:29
    Programovou oflline verzi seriálu naleznete ke stažení na...
  • Pavel Polívka 24. 11. 2007 13:51:07
    Programovou oflline verzi seriálu naleznete ke stažení na...
  • kuthy 30. 1. 2006 19:46:17
    zdravím nešlo by nějak najednou stáhnou všechny stránky o C#.net?díky
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ý | 78

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ý | 119