Transakcie v typovom DataSet-e

Publikoval Michal Kočí dňa 7.8.2007 o 19:47 v kategórii .Net

Ak používate typový dataset v .Net 2.0, potom ste si možno všimli jednu nevýhodu. Defaultne totiž neviete TableAdapter použiť v transakcii. Podpora transakcií sa však vďaka parciálnym triedam dá doplniť a to docela jednoducho. A keď si spravíte pomocnú triedu, tak bude pre Vás použitie transakcie z biznis logiky veľmi ľahké.

Kde je vlastne problém? Problém je, že TablaAdapter pred Vami schováva spojenie (SqlConnection) a príkazy (SqlCommand). Spojenie potrebujete na to, aby ste transakciu mohli otvoriť a príkaz ktorý sa vykonáva na spojení musí mať túto transakciu nastavenú.

Prvý krok teda je pridať si parciálnu triedu. Predpokladajme napríklad, že máte table Adapter nazvaný ActionTableAdapter v datasete MainDataSet. Potom je Adapter v namespace MainDataSetTableAdapters a triedu nadeklarujte nasledovne:

namespace DataAccessLogic.MainDataSetTableAdapters
{
  partial class ActionTableAdapter : ITransactionable
  {
  }
}

Takúto parciálnu triedu vytváram pre každý TableAdapter v DataSet-e (najjednoduchšie je použiť na to CodeSmith) kde sa dá šablóna vytvoriť raz-dva. Rozhranie ITransactionable je deklarované nasledovne:

namespace DataAccessLogic
{
  public interface ITransactionable
  {
    SqlConnection CurrentConnection { get; set; }
    SqlTransaction CurrentTransaction { get; set; }
  }
}

Slúži pre pomocnú triedu, ktorá nám zjednoší otvorenie a uzatvorenie transakcie. Ak si chcete transakciu manažovať sami tak je toto rozhranie pre Vás zbytočné. V každom prípade na povolenie transakcií si aj tak musíte sprístupniť spojenie a buď príkaz alebo transakciu príkazu. Spojenie si môžete sprístupniť nasledovne:

public SqlConnection CurrentConnection
{
  get { return this.Connection; }
  set { this.Connection = value; }
}

S transakciami je to trochu zložitejšie tým, že každý TableAdapter môže obsahovať štyri základné príkazy (pre Select, Insert, Update a Delete) a potom niekoľko pre iné, dodatočné príkazy (query). Preto pri nastavovaní (setter) je potrebné nastaviť transakciu všetkým z nich. Pri získavaní transakcie (getter) ja osobne načítavam transakciu z jedného zo štyroch základných príkazov, konkrétne zo select príkazu vychádzajúc z predpokladu, že keď mám v DataSet-e TableAdapter tak do neho získavam dáta a to práve select metódou:

public SqlTransaction CurrentTransaction
{
  get
  {
    if ((Adapter != null) && (Adapter.SelectCommand != null))
    {
      return Adapter.SelectCommand.Transaction;
    }
    else
    {
      return null;
    }
  }
  set
  {
    if (Adapter.SelectCommand != null) Adapter.SelectCommand.Transaction = value;
    if (Adapter.InsertCommand != null) Adapter.InsertCommand.Transaction = value;
    if (Adapter.UpdateCommand != null) Adapter.UpdateCommand.Transaction = value;
    if (Adapter.DeleteCommand != null) Adapter.DeleteCommand.Transaction = value;

    foreach (SqlCommand command in CommandCollection)
    {
      command.Transaction = value;
    }
  }
}

Pomocná trieda, ktorú som si vytvoril potom v konštruktore očakáva všetky TableAdapter-i, ktoré chcem aby boli súčasťou transakcie (v skutočnosti očakáva pole objektov ktorých triedy implementujú rozhranie Itransactionable). Táto trieda si tiež drží referenciu na spojenie a na transakciu:

namespace DataAccessLogic
{
  public class TransactionWrapper
  {
    private ITransactionable[] adapters;
    private SqlConnection connection;
    private SqlTransaction transaction;

    public TransactionWrapper(ITransactionable[] adapters)
    {
      if ((adapters == null) || (adapters.Length < 1))
      {
        throw new ArgumentException("Number of adapters must be at least 1.");
      }
      this.adapters = adapters;
    }
  }
}

Samotnú transakciu štartujem až keď je zavolaná metóda Start, ktoré vezme spojenie z prvého adaptéru, ak je zatvorené tak ho otvorí (transakcia sa nedá otvoriť na zatvorenom spojení) a začne na nom transakciu. Potom nastaví všetkým adaptérom toto spojenie aj transakciu (transakcia môže byť iba na jednom spojení a všetky adaptéry majú defaultne rozdielne spojenie):

public void Start()
{
  connection = adapters[0].CurrentConnection;
  if (connection.State != ConnectionState.Open)
  {
    connection.Open();
  }

  transaction = connection.BeginTransaction();

  foreach (ITransactionable adapter in adapters)
  {
    adapter.CurrentConnection = connection;
    adapter.CurrentTransaction = transaction;
  }
}

Ako poslednú metódu obsahuje trieda TransactionWrapper metódu Finish, ktorá podľa parametra commit transakciu potvrdí (commit) alebo odroluje späť (rollback):

public void Finish(bool commit)
{
  if (commit)
  {
    transaction.Commit();
  }
  else
  {
    transaction.Rollback();
  }
}

No a keď chcem pustiť viacero príkazov v jednej transakcii, potom používam TransactionWrapper nasledovne:

bool commit = true;

PluginTableAdapter pluginAdapter = new PluginTableAdapter();
FileDependencyTableAdapter dependencyAdapter = new FileDependencyTableAdapter();
TransactionWrapper wrapper = 
  new TransactionWrapper(new ITransactionable[2] 
    { pluginAdapter, dependencyAdapter });

wrapper.Star();

try
{
  // volanie metód adaptéra, napríklad:
  // int id = pluginAdapter.Insert(pluginName, fileName);
  // dependencyAdapter.Insert(id, 1);
  // dependencyAdapter.Insert(id, 2);
}
Catch(Exception ex)
{
  ExceptionPolicy.HandleException(ex, "Exception Policy");
  commit = false;
}

wrapper.Finish(commit);

Len škoda, že nejaké poriadne riešenie nie je priamo súčasťou .Net 2.0, bolo by to jednoduchšie než takto obchádzať takéto nedostatky...

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.

Komentáre

K tomuto článku nie su pridané žiadne komentáre.

Pridať komentár

Máš niečo zaujímavé povedať k článku? Pridaj to k článku ako komentár. Spam, reklamu alebo inak nerelevantné komentáre okamžite mažem.