Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.
Podstawową funkcją transakcji jest zagwarantowanie zasad ACID:
- atomowości (ang. atomicity),
- spójności (ang. consistency),
- izolacji (ang. isolation),
- trwałości (ang. durability).
Kiedy operacje związane z bazą odbywają się na wielu maszynach i wielu zbiorach danych, nie jest to takie proste. WCF wspomaga programistę w tym zadaniu.
W celu spełnienia zasad ACID najczęstszym podejściem jest wykorzystanie dwuetapowego zgłoszenia (ang. two-phase commit):
- Etap przygotowania (ang. prepare phase) – koordynator transakcji zarządza tym etapem, wysyła żądanie przygotowania do wszystkich zarządców transakcji (ang. transaction manager), na maszynach biorących udział w procesie. Zarządcy odsyłają informację o tym, czy operacje zakończyły się sukcesem, czy porażką. Kiedy wszyscy odpowiedzą etap przygotowania uznajemy za zakończony.
- Etap zgłoszenia (ang. commit phase) – zależny od wyników etapu poprzedniego; jeżeli tamten zakończy się sukcesem – wysyłane zostaje żądanie Commit, w przeciwnym wypadku, jeżeli chociaż jedna maszyna zwróci komunikat błędu – koordynator transakcji wysyła żądanie Abort, aby poinformować zarządców o potrzebie cofnięcia zmian.
W zależności od sytuacji wykorzystany może zostać jeden z trzech zarządców transakcji:
- The Lightweight Transaction Manager (LTM) – wprowadzony w .NET 2.0 przez przestrzeń nazw System.Transaction (do projektu trzeba dodać assembly); poniższy przykład aktualizuje bazę danych w ramach lekkiej transakcji. W celu zgłoszenia transakcji wywołana jest metoda Complete:
1: using (TransactionScope ts = new TransactionScope())
2: {
3: using (SqlConnection cn1 = new SqlConnection(connectionString))
4: {
5: insertRecord(cn1, "User1");
6: using(SqlConnection cn2 = new SqlConnection(connectionString))
7: {
8: insertRecord(cn2, "User2");
9: }
10: }
11: ts.Complete();
12: }
13: private void insertRecord(SqlConnection cn, string userName)
14: {
15: SqlCommand cmd = new SqlCommand(String.Format("Insert INTO [Users]" +" VALUES('{0}')", userName), cn);
16: cn.Open();
17: cmd.ExecuteNonQuery();
18: }
- OLE Transactions (OleTx),
- WS-Atomic Transactions (WS-AT).
Transakcji możemy pozwolić na działanie poza granicami serwisu, lub nie. Decyzję o tym podejmują klient i serwis, jednak jeżeli serwis wymaga transakcji, blokowanie po stronie klienta spowoduje błąd aplikacji. Ustawione może to zostać w bindingu za pomocą atrybutu TransactionFlow, zarówno imperatywnie jak i deklaratywnie:
1: // C#
2: WSHttpBinding binding = new WSHttpBinding();
3: binding.TransactionFlow = true;
4:
5: <!--XML-->
6: <bindings>
7: <wsHttpBinding>
8: <binding name="Transactional" transactionFlow="true" />
9: </wsHttpBinding>
10: </bindings>
Dodatkowo wymagane jest, aby operacje serwisu były oznaczone atrybutem TransactionFlow, który wskazuje na to, że mogą one brać udział w transakcji:
1: [ServiceContract]
2: public interface IDemoContract
3: {
4: [OperationContract]
5: [TransactionFlow(TransactionFlowOption.Allowed)]
6: void TransactedMethod(...);
7: }
Po stronie klienta w ten sam sposób zostanie udekorowana klasa proxy:
1: public class DemoService : IDemoContract
2: {
3: [TransactionFlow(TransactionFlowOption.Allowed)]
4: public void TransactedMethod(...)
5: {...}
6: }
W powyższym przykładzie TransactionFlow ustawiony jest na wartość TransactionFlowOption.Allowed, która pozwala transakcji klienta przechodzić do serwisu. Domyślna opcja TransactionFlowOption.NotAllowed sprawia, że żadna transakcja nie jest przesyłana.
Uwaga: Transakcje nie działają w przypadku metod typu one-way – bez komunikatu zwrotnego nie ma możliwości stworzenia rozproszonej transakcji.