Co to znaczy, że SqlConnection może zostać „zarejestrowany” w transakcji? Czy to po prostu oznacza, że polecenia, które wykonam na połączeniu, będą uczestniczyć w transakcji?
Jeśli tak, to w jakich okolicznościach SqlConnection jest automatycznie rejestrowany w otaczającej TransactionScope Transaction?
Zobacz pytania w komentarzach do kodu. Domyślam się, że odpowiedź na każde pytanie wynika z każdego pytania w nawiasie.
Scenariusz 1: Otwieranie połączeń WEWNĄTRZ zakresu transakcji
using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{
// Q1: Is connection automatically enlisted in transaction? (Yes?)
//
// Q2: If I open (and run commands on) a second connection now,
// with an identical connection string,
// what, if any, is the relationship of this second connection to the first?
//
// Q3: Will this second connection's automatic enlistment
// in the current transaction scope cause the transaction to be
// escalated to a distributed transaction? (Yes?)
}
Scenariusz 2: Korzystanie z połączeń WEWNĄTRZ zakresu transakcji, który został otwarty POZA nim
//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
// Connection was opened before transaction scope was created
// Q4: If I start executing commands on the connection now,
// will it automatically become enlisted in the current transaction scope? (No?)
//
// Q5: If not enlisted, will commands I execute on the connection now
// participate in the ambient transaction? (No?)
//
// Q6: If commands on this connection are
// not participating in the current transaction, will they be committed
// even if rollback the current transaction scope? (Yes?)
//
// If my thoughts are correct, all of the above is disturbing,
// because it would look like I'm executing commands
// in a transaction scope, when in fact I'm not at all,
// until I do the following...
//
// Now enlisting existing connection in current transaction
conn.EnlistTransaction( Transaction.Current );
//
// Q7: Does the above method explicitly enlist the pre-existing connection
// in the current ambient transaction, so that commands I
// execute on the connection now participate in the
// ambient transaction? (Yes?)
//
// Q8: If the existing connection was already enlisted in a transaction
// when I called the above method, what would happen? Might an error be thrown? (Probably?)
//
// Q9: If the existing connection was already enlisted in a transaction
// and I did NOT call the above method to enlist it, would any commands
// I execute on it participate in it's existing transaction rather than
// the current transaction scope. (Yes?)
}
Dobra robota, Triynko, wszystkie twoje odpowiedzi wydają mi się dość dokładne i kompletne. Kilka innych rzeczy, na które chciałbym zwrócić uwagę:
(1) Rejestracja ręczna
W powyższym kodzie pokazujesz (poprawnie) ręczne rejestrowanie w następujący sposób:
Jednak można to również zrobić w ten sposób, używając Enlist = false w ciągu połączenia.
Należy zwrócić uwagę na jeszcze jedną rzecz. Po otwarciu conn2 kod puli połączeń nie wie, że chcesz go później zarejestrować w tej samej transakcji, co conn1, co oznacza, że conn2 otrzymuje inne połączenie wewnętrzne niż conn1. Następnie, gdy rejestrowane jest conn2, rejestrowane są teraz 2 połączenia, więc transakcja musi być promowana do MSDTC. Tej promocji można uniknąć tylko przy użyciu automatycznej rejestracji.
(2) Przed .Net 4.0 bardzo polecam ustawienie „Transaction Binding = Explicit Unbind” w ciągu połączenia . Ten problem został rozwiązany w .Net 4.0, dzięki czemu Explicit Unbind jest całkowicie niepotrzebny.
(3) Przetaczanie własnych
CommittableTransaction
i ustawianieTransaction.Current
do tego jest zasadniczo tym samym, coTransactionScope
to, co robi. Jest to rzadko przydatne, po prostu FYI.(4)
Transaction.Current
jest statyczny. Oznacza to, żeTransaction.Current
jest ustawiony tylko w wątku, który utworzyłTransactionScope
. Tak więc wiele wątków wykonujących to samoTransactionScope
(prawdopodobnie wykorzystującychTask
) nie jest możliwe.źródło
Inną dziwaczną sytuacją, którą widzieliśmy, jest to, że jeśli
EntityConnectionStringBuilder
ją zbudujesz, to się zepsujeTransactionScope.Current
i (naszym zdaniem) zarejestruje się w transakcji. Zauważyliśmy to w debugger, gdzieTransactionScope.Current
„scurrent.TransactionInformation.internalTransaction
pokazyenlistmentCount == 1
przed konstruowania, aenlistmentCount == 2
potem.Aby tego uniknąć, zbuduj go w środku
using (new TransactionScope(TransactionScopeOption.Suppress))
i być może poza zakresem twojej operacji (budowaliśmy to za każdym razem, gdy potrzebowaliśmy połączenia).
źródło