Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Klasa TransactionScope zapewnia prosty sposób oznaczania bloku kodu jako udziału w transakcji bez konieczności interakcji z samą transakcją. Zakres transakcji może automatycznie wybrać otoczenia transakcji i zarządzać nią. Ze względu na łatwość użycia i wydajność zaleca się użycie TransactionScope klasy podczas tworzenia aplikacji transakcyjnej.
Ponadto nie trzeba wyraźnie przypisywać zasobów do transakcji. Każdy System.Transactions menedżer zasobów (taki jak SQL Server 2005) może wykryć istnienie transakcji środowiskowej utworzonej w zakresie i automatycznie się przyłączyć.
Tworzenie zakresu transakcji
Poniższy przykład przedstawia proste użycie TransactionScope klasy.
// This function takes arguments for 2 connection strings and commands to create a transaction
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
// transaction is rolled back. To test this code, you can connect to two different databases
// on the same server by altering the connection string, or to another 3rd party RDBMS by
// altering the code in the connection2 code block.
static public int CreateTransactionScope(
string connectString1, string connectString2,
string commandText1, string commandText2)
{
// Initialize the return value to zero and create a StringWriter to display results.
int returnValue = 0;
System.IO.StringWriter writer = new System.IO.StringWriter();
try
{
// Create the TransactionScope to execute the commands, guaranteeing
// that both commands can commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Create the SqlCommand object and execute the first command.
SqlCommand command1 = new SqlCommand(commandText1, connection1);
returnValue = command1.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
// If you get here, this means that command1 succeeded. By nesting
// the using block for connection2 inside that of connection1, you
// conserve server and network resources as connection2 is opened
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
{
// The transaction is escalated to a full distributed
// transaction when connection2 is opened.
connection2.Open();
// Execute the second command in the second database.
returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
' This function takes arguments for 2 connection strings and commands to create a transaction
' involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
' transaction is rolled back. To test this code, you can connect to two different databases
' on the same server by altering the connection string, or to another 3rd party RDBMS
' by altering the code in the connection2 code block.
Public Function CreateTransactionScope( _
ByVal connectString1 As String, ByVal connectString2 As String, _
ByVal commandText1 As String, ByVal commandText2 As String) As Integer
' Initialize the return value to zero and create a StringWriter to display results.
Dim returnValue As Integer = 0
Dim writer As System.IO.StringWriter = New System.IO.StringWriter
Try
' Create the TransactionScope to execute the commands, guaranteeing
' that both commands can commit or roll back as a single unit of work.
Using scope As New TransactionScope()
Using connection1 As New SqlConnection(connectString1)
' Opening the connection automatically enlists it in the
' TransactionScope as a lightweight transaction.
connection1.Open()
' Create the SqlCommand object and execute the first command.
Dim command1 As SqlCommand = New SqlCommand(commandText1, connection1)
returnValue = command1.ExecuteNonQuery()
writer.WriteLine("Rows to be affected by command1: {0}", returnValue)
' If you get here, this means that command1 succeeded. By nesting
' the using block for connection2 inside that of connection1, you
' conserve server and network resources as connection2 is opened
' only when there is a chance that the transaction can commit.
Using connection2 As New SqlConnection(connectString2)
' The transaction is escalated to a full distributed
' transaction when connection2 is opened.
connection2.Open()
' Execute the second command in the second database.
returnValue = 0
Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2)
returnValue = command2.ExecuteNonQuery()
writer.WriteLine("Rows to be affected by command2: {0}", returnValue)
End Using
End Using
' The Complete method commits the transaction. If an exception has been thrown,
' Complete is called and the transaction is rolled back.
scope.Complete()
End Using
Catch ex As TransactionAbortedException
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
End Try
' Display messages.
Console.WriteLine(writer.ToString())
Return returnValue
End Function
Zakres transakcji jest uruchamiany po utworzeniu nowego TransactionScope obiektu. Jak pokazano w przykładzie kodu, zaleca się tworzenie zakresów za pomocą instrukcji using
. Instrukcja using
jest dostępna zarówno w języku C#, jak i w Visual Basic, i działa jak blok try
...finally
zapewniający prawidłowe zwolnienie zasobów w obrębie zakresu.
Podczas tworzenia TransactionScope menedżer transakcji określa, w której transakcji ma uczestniczyć. Po ustaleniu ten zakres zawsze uczestniczy w danej transakcji. Decyzja jest oparta na dwóch czynnikach: czy transakcja środowiskowa jest obecna oraz wartość parametru TransactionScopeOption
w konstruktorze. Otoczna transakcja to ta transakcja, w ramach której wykonywany jest kod. Odwołanie do transakcji otoczenia można uzyskać, wywołując statyczną właściwość Transaction.Current klasy Transaction. Aby uzyskać więcej informacji na temat sposobu używania tego parametru, zobacz sekcję Zarządzanie przepływem transakcji przy użyciu transactionScopeOption w tym temacie.
Kończenie zakresu transakcji
Gdy aplikacja ukończy całą pracę, którą chce wykonać w transakcji, należy wywołać TransactionScope.Complete metodę tylko raz, aby poinformować menedżera transakcji, że dopuszczalne jest zatwierdzenie transakcji. Bardzo dobrą praktyką jest umieszczenie wywołania Complete jako ostatniej instrukcji w bloku using
.
Nie wywołanie tej metody przerywa transakcję, ponieważ menedżer transakcji interpretuje to jako awarię systemu lub odpowiednik wyjątku zgłaszanego w ramach transakcji. Jednak wywołanie tej metody nie gwarantuje, że transakcja zostanie zatwierdzona. Jest to tylko sposób informowania menedżera transakcji o stanie. Po wywołaniu metody Complete nie można już uzyskać dostępu do transakcji kontekstowej za pomocą właściwości Current, a próba zrobienia tego spowoduje wyrzucenie wyjątku.
TransactionScope Jeśli obiekt początkowo utworzył transakcję, rzeczywista praca zatwierdzania transakcji przez menedżera transakcji następuje po ostatnim wierszu kodu w bloku using
. Jeśli transakcja nie została utworzona, zatwierdzenie odbywa się za każdym razem, gdy Commit jest wywoływane przez właściciela CommittableTransaction obiektu. W tym momencie menedżer transakcji wywołuje menedżerów zasobów i informuje ich, aby zatwierdzić lub wycofać transakcję, w zależności od tego, czy metoda Complete została wywołana na obiekcie TransactionScope.
Instrukcja using
zapewnia, że Dispose metoda TransactionScope obiektu jest wywoływana, nawet jeśli wystąpi wyjątek. Metoda Dispose oznacza koniec zakresu transakcji. Wyjątki występujące po wywołaniu tej metody mogą nie mieć wpływu na transakcję. Ta metoda przywraca również bieżącą transakcję do jej poprzedniego stanu.
Zgłoszony jest TransactionAbortedException, jeśli zakres tworzy transakcję, a transakcja jest przerwana. Element TransactionInDoubtException jest zgłaszany, jeśli menedżer transakcji nie może podjąć decyzji zatwierdzenia. Żaden wyjątek nie jest zgłaszany, jeśli transakcja jest zatwierdzona.
Wycofywanie transakcji
Jeśli chcesz wycofać transakcję, nie powinieneś wywoływać metody Complete w zakresie transakcji. Na przykład możesz zgłosić wyjątek w zakresie. Transakcja, w której uczestniczy, zostanie wycofana.
Zarządzanie przepływem transakcji przy użyciu transactionScopeOption
Zakres transakcji można zagnieżdżać przez wywołanie metody, która używa TransactionScope z poziomu metody, która korzysta ze swojego własnego zakresu, tak jak w przypadku metody RootMethod
w poniższym przykładzie.
void RootMethod()
{
using(TransactionScope scope = new TransactionScope())
{
/* Perform transactional work here */
SomeMethod();
scope.Complete();
}
}
void SomeMethod()
{
using(TransactionScope scope = new TransactionScope())
{
/* Perform transactional work here */
scope.Complete();
}
}
Najbardziej górny zakres transakcji jest określany jako zakres główny.
Klasa TransactionScope udostępnia kilka przeciążonych konstruktorów, które akceptują wyliczenie typu TransactionScopeOption, które definiuje transakcyjne zachowanie obszaru.
Obiekt TransactionScope ma trzy opcje:
Dołącz do bieżącej transakcji lub utwórz nową, jeśli taka nie istnieje.
Stań się nowym zakresem głównym, to znaczy rozpocznij nową transakcję, a ta transakcja będzie nową transakcją kontekstową we własnym zakresie.
Nie brać udziału w transakcji całkowicie. W rezultacie nie ma otoczenia transakcji.
Jeśli zakres jest tworzony za pomocą Required, a transakcja środowiskowa jest aktywna, zakres dołącza do tej transakcji. Jeśli natomiast nie ma transakcji kontekstowej, zakres tworzy nową transakcję i staje się zakresem głównym. Jest to wartość domyślna. Gdy Required jest używany, kod wewnątrz zakresu nie musi zachowywać się inaczej, niezależnie od tego, czy jest to element główny, czy po prostu łączący otoczenia transakcji. Powinien działać identycznie w obu przypadkach.
Jeśli zakres jest tworzony za pomocą RequiresNew, zawsze jest to zakres główny. Rozpoczyna nową transakcję, a jej transakcja staje się nową transakcją środowiskową wewnątrz zakresu.
Jeśli zakres zostanie zainicjowany za pomocą Suppress, nigdy nie bierze udziału w transakcji, niezależnie od tego, czy jest obecna transakcja środowiskowa. Zakres zainicjowany przy użyciu tej wartości zawsze ma null
jako transakcję środowiska.
Powyższe opcje zostały podsumowane w poniższej tabeli.
OpcjaZakresuTransakcji | Transakcja w tle | Zakres obejmuje |
---|---|---|
Wymagane | Nie. | Nowa transakcja (będzie podstawą) |
Wymaga nowego | Nie. | Nowa transakcja (będzie podstawą) |
Tłumić | Nie. | Brak transakcji |
Wymagane | Tak | Transakcja w tle |
Wymaga nowego | Tak | Nowa transakcja (będzie podstawą) |
Tłumić | Tak | Brak transakcji |
Gdy obiekt TransactionScope dołącza do istniejącej transakcji otoczenia, rozporządzanie obiektem zakresu może nie zakończyć transakcji, chyba że zakres przerwie transakcję. Jeśli transakcja otoczenia została utworzona przez zakres główny, dopiero gdy zakres główny zostanie usunięty, wywoływane jest Commit w transakcji. Jeśli transakcja została utworzona ręcznie, transakcja kończy się, gdy zostanie przerwana lub zatwierdzona przez jej twórcę.
W poniższym przykładzie pokazano TransactionScope obiekt, który tworzy trzy zagnieżdżone obiekty zakresu, z których każde jest instancjonowane z inną TransactionScopeOption wartością.
using(TransactionScope scope1 = new TransactionScope())
//Default is Required
{
using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
{
//...
}
using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))
{
//...
}
using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
{
//...
}
}
W przykładzie pokazano blok kodu bez żadnej otaczającej transakcji, tworząc nowy zakres (scope1
) za pomocą Required. Zakres scope1
jest zakresem głównym, ponieważ tworzy nową transakcję (Transaction A) i sprawia, że transakcja A staje się transakcją otoczenia.
Scope1
następnie tworzy trzy kolejne obiekty, z których każda ma inną TransactionScopeOption wartość. Na przykład scope2
jest tworzony za pomocą Required, a ponieważ istnieje transakcja środowiskowa, łączy się z pierwszą transakcją utworzoną przez scope1
. Należy pamiętać, że scope3
jest podstawowym zakresem nowej transakcji i że scope4
nie ma żadnej bieżącej transakcji.
Chociaż wartością, która jest domyślna i najczęściej używana, jest TransactionScopeOption, każda z pozostałych wartości ma swój unikatowy cel.
Kod nie transakcyjny wewnątrz zakresu transakcji
Suppress jest przydatny, gdy chcesz zachować operacje wykonywane przez sekcję kodu i nie chcesz zakończyć bieżącej transakcji, jeśli operacje zakończą się niepowodzeniem. Na przykład, gdy chcesz wykonać operacje rejestrowania lub inspekcji; lub gdy chcesz publikować zdarzenia dla subskrybentów niezależnie od tego, czy transakcja otoczenia zostanie zatwierdzona czy przerwana. Ta wartość pozwala na utworzenie sekcji kodu nietransakcyjnego wewnątrz zakresu transakcji, jak pokazano w poniższym przykładzie.
using(TransactionScope scope1 = new TransactionScope())
{
try
{
//Start of non-transactional section
using(TransactionScope scope2 = new
TransactionScope(TransactionScopeOption.Suppress))
{
//Do non-transactional work here
}
//Restores ambient transaction here
}
catch {}
//Rest of scope1
}
Głosowanie wewnątrz zagnieżdżonego zakresu
Chociaż zagnieżdżony zakres może dołączyć do bieżącej transakcji zakresu głównego, wywołanie Complete w zagnieżdżonym zakresie nie ma wpływu na zakres główny. Transakcja zostanie zatwierdzona tylko wtedy, gdy wszystkie zakresy z zakresu głównego do ostatniego zagnieżdżonego zakresu zagłosują, aby zatwierdzić transakcję. Nie wywołanie Complete w zagnieżdżonym zakresie wpłynie na zakres główny, ponieważ transakcja środowiskowa zostanie natychmiast przerwana.
Konfigurowanie limitu czasu TransactionScope
Niektóre przeciążone konstruktory TransactionScope akceptują wartość typu TimeSpan, która służy do kontrolowania limitu czasu transakcji. Limit czasu ustawiony na zero oznacza nieskończony limit czasu. Nieskończony limit czasu jest przydatny głównie w przypadku debugowania, gdy chcesz odizolować problem w logice biznesowej, przechodząc przez kod i nie chcesz, aby transakcja debugowana upłynął limit czasu podczas próby zlokalizowania problemu. Należy zachować szczególną ostrożność przy użyciu nieskończonej wartości limitu czasu we wszystkich innych przypadkach, ponieważ zastępuje zabezpieczenia przed zakleszczeniami transakcji.
Zazwyczaj ustawiasz limit czasu TransactionScope na wartości inne niż domyślne w dwóch przypadkach. Pierwszy to podczas programowania, gdy chcesz przetestować sposób obsługi przerwanych transakcji przez aplikację. Ustawiając limit czasu na małą wartość (np. jedną milisekundę), powodujesz, że twoja transakcja kończy się niepowodzeniem, i w ten sposób możesz obserwować swój kod obsługi błędów. Drugi przypadek, w którym ustawiono wartość mniejszą niż domyślny limit czasu, jest wtedy, gdy uważasz, że zakres jest związany z rywalizacją o zasoby, co powoduje zakleszczenia. W takim przypadku chcesz przerwać transakcję tak szybko, jak to możliwe i nie poczekaj na wygaśnięcie domyślnego limitu czasu.
Gdy zakres dołączy do otoczeniowej transakcji, ale określi mniejszy limit czasu niż ten, który jest ustawiony dla otoczeniowej transakcji, nowy, krótszy limit czasu jest wiążący dla obiektu TransactionScope, a zakres musi zakończyć się w krótszym, określonym czasie, w przeciwnym razie transakcja zostanie automatycznie przerwana. Jeśli limit czasu zagnieżdżonego obszaru jest większy niż limit czasu transakcji otoczenia, nie ma to żadnego znaczenia.
Ustawianie poziomu izolacji TransactionScope
Niektóre przeciążone konstruktory TransactionScope akceptują strukturę typu TransactionOptions do określenia poziomu izolacji oraz wartości limitu czasu. Domyślnie transakcja jest wykonywana z poziomem izolacji ustawionym na Serializable. Wybieranie poziomu izolacji innego niż Serializable jest często używane w systemach intensywnie korzystających z odczytu. Wymaga to solidnego zrozumienia teorii przetwarzania transakcji i semantyki samej transakcji, związanych z nią problemów współbieżności i konsekwencji spójności systemu.
Ponadto nie wszyscy menedżerowie zasobów obsługują wszystkie poziomy izolacji i mogą zdecydować się na udział w transakcji na wyższym poziomie niż skonfigurowany.
Każdy poziom izolacji poza Serializable jest podatny na niespójność wynikającą z innych transakcji uzyskujących dostęp do tych samych informacji. Różnica między różnymi poziomami izolacji jest sposobem użycia blokad odczytu i zapisu. Blokada może być przechowywana tylko wtedy, gdy transakcja uzyskuje dostęp do danych w menedżerze zasobów lub może być przechowywana do momentu zatwierdzenia lub przerwania transakcji. Pierwszy z nich jest lepszy dla przepływności, a drugi w celu zapewnienia spójności. Dwa rodzaje blokad i dwa rodzaje operacji (odczyt/zapis) zapewniają cztery podstawowe poziomy izolacji. Aby uzyskać więcej informacji, zobacz IsolationLevel.
W przypadku korzystania z obiektów zagnieżdżonych TransactionScope wszystkie zagnieżdżone zakresy muszą być skonfigurowane tak, aby używały dokładnie tego samego poziomu izolacji, jeśli chcą dołączyć do otaczającej transakcji. Jeśli zagnieżdżony TransactionScope obiekt próbuje przyłączyć się do otaczającej transakcji, ale określa inny poziom izolacji, ArgumentException jest wyrzucany.
Współdziałanie z COM+
Podczas tworzenia nowej instancji TransactionScope, można użyć EnterpriseServicesInteropOption wyliczenia w jednym z konstruktorów, aby określić sposób interakcji z COM+. Aby uzyskać więcej informacji na ten temat, zobacz Współdziałanie z usługami przedsiębiorstwa i transakcjami COM+ .