Transakcje w .net

144

Jakie są najlepsze praktyki wykonywania transakcji w C # .Net 2.0. Jakie klasy powinny być używane? Jakie są pułapki, na które należy zwrócić uwagę itp. Wszystkie te rzeczy dotyczące zatwierdzania i wycofywania. Właśnie zaczynam projekt, w którym może być konieczne wykonanie pewnych transakcji podczas wstawiania danych do bazy danych. Wszelkie odpowiedzi lub linki nawet do podstawowych informacji na temat transakcji są mile widziane.

Malik Daud Ahmad Khokhar
źródło
Oto dobry przykład Transakcji w .NET na Codeproject do użycia jako początek.
Mitchel Sellers

Odpowiedzi:

271

Istnieją 2 główne rodzaje transakcji; transakcje połączeń i transakcje otoczenia. Transakcja połączenia (taka jak SqlTransaction) jest powiązana bezpośrednio z połączeniem db (takim jak SqlConnection), co oznacza, że ​​musisz nadal przekazywać połączenie - w niektórych przypadkach OK, ale nie pozwala na „tworzenie / używanie / zwalnianie” użycia i nie zezwala na pracę między bazami danych. Przykład (sformatowany dla spacji):

using (IDbTransaction tran = conn.BeginTransaction()) {
    try {
        // your code
        tran.Commit();
    }  catch {
        tran.Rollback();
        throw;
    }
}

Nie jest zbyt niechlujny, ale ogranicza się do naszego połączenia "Conn". Jeśli chcemy odwołać się do różnych metod, musimy teraz przekazać „conn”.

Alternatywą jest transakcja otoczenia; Nowość w .NET 2.0, obiekt TransactionScope (System.Transactions.dll) umożliwia korzystanie z szeregu operacji (odpowiedni dostawcy będą automatycznie rejestrować się w transakcjach otoczenia). Ułatwia to retro dopasowanie do istniejącego (nietransakcyjnego) kodu i rozmowę z wieloma dostawcami (chociaż DTC zaangażuje się, jeśli porozmawiasz z więcej niż jednym).

Na przykład:

using(TransactionScope tran = new TransactionScope()) {
    CallAMethodThatDoesSomeWork();
    CallAMethodThatDoesSomeMoreWork();
    tran.Complete();
}

Zauważ tutaj, że te dwie metody mogą obsługiwać własne połączenia (otwórz / użyj / zamknij / usuń), ale po cichu staną się częścią transakcji otoczenia bez konieczności przekazywania czegokolwiek.

Jeśli twój kod zawiera błędy, Dispose () zostanie wywołana bez Complete (), więc zostanie wycofana. Obsługiwane jest oczekiwane zagnieżdżanie itp., Chociaż nie można cofnąć transakcji wewnętrznej, ale zakończyć transakcję zewnętrzną: jeśli ktoś jest niezadowolony, transakcja jest przerywana.

Inną zaletą TransactionScope jest to, że nie jest on powiązany tylko z bazami danych; każdy dostawca obsługujący transakcje może z niego korzystać. Na przykład WCF. Lub istnieją nawet modele obiektów kompatybilne z TransactionScope (np. Klasy .NET z możliwością wycofania - być może łatwiejsze niż pamiątka, chociaż sam nigdy nie korzystałem z tego podejścia).

W sumie bardzo, bardzo przydatny obiekt.

Kilka zastrzeżeń:

  • W SQL Server 2000 TransactionScope natychmiast przejdzie do usługi DTC; jest to naprawione w SQL Server 2005 i nowszych, może używać LTM (znacznie mniej narzutu), dopóki nie porozmawiasz z 2 źródłami itp., kiedy zostanie podniesiony do DTC.
  • Wystąpiła usterka, która oznacza, że ​​może być konieczne dostosowanie parametrów połączenia
Marc Gravell
źródło
CSLA .NET 2.0 obsługuje obiekt TransactionScope!
Binoj Antony
Problem polega na tym, że masz transakcję w pierwszej metodzie i ta metoda (hermetyzacja) nie wie, czy zostanie wywołana z transakcji nadrzędnej, czy nie.
Eduardo Molteni
1
@Eduardo - nie stanowi to problemu przy korzystaniu z TransactionScope, co czyni go bardzo atrakcyjnym. Takie transakcje zagnieżdżają się i zatwierdza się tylko najbardziej zewnętrzna.
Marc Gravell
Mam nadzieję, że nadal słuchasz. Powiedziałeś, że istnieją "pewne modele obiektów kompatybilne z TransactionScope". Czy możesz wskazać mi niektóre z nich? Dzięki.
majkinetor
1
Znowu Marc, kolejne doskonałe wyjaśnienie. Kiedy mówisz „oczekiwane zagnieżdżanie jest obsługiwane”, czy dotyczy to bloków transakcji zdefiniowanych w samych metodach (na przykład CallAMethodThatDoesSomeWork ())? Czy jeśli zakres transakcji jest zdefiniowany na zewnątrz, nie jest to wymagane?
Phil Cooper,
11
protected void Button1_Click(object sender, EventArgs e)
   {


       using (SqlConnection connection1 = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database.mdf;Integrated Security=True;User Instance=True"))
       {
           connection1.Open();

           // Start a local transaction.
           SqlTransaction sqlTran = connection1.BeginTransaction();

           // Enlist a command in the current transaction.
           SqlCommand command = connection1.CreateCommand();
           command.Transaction = sqlTran;

           try
           {
               // Execute two separate commands.
               command.CommandText =
                "insert into [doctor](drname,drspecialization,drday) values ('a','b','c')";
               command.ExecuteNonQuery();
               command.CommandText =
                "insert into [doctor](drname,drspecialization,drday) values ('x','y','z')";
               command.ExecuteNonQuery();

               // Commit the transaction.
               sqlTran.Commit();
               Label3.Text = "Both records were written to database.";
           }
           catch (Exception ex)
           {
               // Handle the exception if the transaction fails to commit.
               Label4.Text = ex.Message;


               try
               {
                   // Attempt to roll back the transaction.
                   sqlTran.Rollback();
               }
               catch (Exception exRollback)
               {
                   // Throws an InvalidOperationException if the connection 
                   // is closed or the transaction has already been rolled 
                   // back on the server.
                   Label5.Text = exRollback.Message;

               }
           }
       }


   }
Ali Gholizadeh
źródło
4

Możesz również zawrzeć transakcję we własnej procedurze składowanej i obsłużyć ją w ten sposób, zamiast wykonywać transakcje w samym C #.

Charles Graham
źródło
1

jeśli potrzebujesz go tylko do rzeczy związanych z db, niektóre OR Mappers (np. NHibernate) domyślnie obsługują transactinos po wyjęciu z pudełka.

Joachim Kerschbaumer
źródło
0

Zależy to również od tego, czego potrzebujesz. W przypadku podstawowych transakcji SQL możesz spróbować wykonać transakcje TSQL, używając w swoim kodzie BEGIN TRANS i COMMIT TRANS. To najłatwiejszy sposób, ale ma złożoność i musisz uważać, aby poprawnie zatwierdzić (i wycofać).

Użyłbym czegoś takiego

SQLTransaction trans = null;
using(trans = new SqlTransaction)
{
    ...
    Do SQL stuff here passing my trans into my various SQL executers
    ...
    trans.Commit  // May not be quite right
}

Jakakolwiek awaria wyskoczy Cię bezpośrednio z usingtransakcji, a transakcja zawsze zostanie zatwierdzona lub wycofana (w zależności od tego, co jej powiesz). Największym problemem, z jakim mieliśmy do czynienia, było upewnienie się, że zawsze się to zgadza. Zastosowanie zapewnia ograniczenie zakresu transakcji.

Brody
źródło