Unter welchen Umständen wird eine SqlConnection automatisch in eine Ambient TransactionScope-Transaktion eingetragen?

Was bedeutet es für eine SqlConnection, in einer Transaktion “eingetragen” zu werden? Bedeutet es einfach, dass Befehle, die ich auf der Verbindung ausführe, an der Transaktion teilnehmen?

Wenn ja, unter welchen Umständen wird eine SqlConnection automatisch in eine Ambient TransactionScope-Transaktion eingetragen?

Siehe Fragen in Code-Kommentaren. Meine Antwort auf die Antwort jeder Frage folgt jeder Frage in Klammern.

Szenario 1: Verbindungen innerhalb eines Transaktionsbereichs öffnen

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?) } 

Szenario 2: Verwendung von Verbindungen innerhalb eines Transaktionsbereichs, die außerhalb davon geöffnet wurden

 //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?) } 

Solutions Collecting From Web of "Unter welchen Umständen wird eine SqlConnection automatisch in eine Ambient TransactionScope-Transaktion eingetragen?"

Ich habe einige Tests gemacht, seit ich diese Frage gestellt habe, und habe die meisten, wenn nicht alle Antworten selbst gefunden, da niemand anders geantwortet hat. Bitte lassen Sie mich wissen, wenn ich etwas verpasst habe.

Q1. Ja, es sei denn, “enlist = false” ist in der Verbindungszeichenfolge angegeben. Der Verbindungspool findet eine verwendbare Verbindung. Eine verwendbare Verbindung ist eine, die nicht in einer Transaktion eingetragen ist oder in derselben Transaktion eingetragen ist.

Q2. Die zweite Verbindung ist eine unabhängige Verbindung, die an derselben Transaktion teilnimmt. Ich bin mir nicht sicher über die Interaktion von Befehlen auf diesen beiden Verbindungen, da sie auf der gleichen database ausgeführt werden, aber ich denke, Fehler können auftreten, wenn Befehle auf beiden gleichzeitig ausgegeben werden: Fehler wie “Transaktionskontext in Verwendung durch eine andere Sitzung ”

Q3. Ja, es wird an eine verteilte Transaktion eskaliert. Wenn Sie also mehr als eine Verbindung mit derselben Verbindungszeichenfolge verknüpfen, wird sie zu einer verteilten Transaktion, die durch Überprüfen auf eine GUID ungleich null bei Transaction.Current.TransactionInformation bestätigt werden kann .DistributedIdentifier. * Update: Ich habe irgendwo gelesen, dass dies in SQL Server 2008 behoben ist, so dass MSDTC nicht verwendet wird, wenn die gleiche Verbindungszeichenfolge für beide Verbindungen verwendet wird (solange beide Verbindungen nicht gleichzeitig geöffnet sind). Dadurch können Sie eine Verbindung öffnen und mehrmals innerhalb einer Transaktion schließen, wodurch der Verbindungspool besser genutzt werden kann, indem Verbindungen so spät wie möglich geöffnet und so bald wie möglich geschlossen werden.

Q4. Nein. Eine Verbindung, die geöffnet wurde, wenn kein Transaktionsbereich aktiv war, wird nicht automatisch in einen neu erstellten Transaktionsbereich aufgenommen.

Q5. Nein. Wenn Sie keine Verbindung im Transaktionsbereich öffnen oder eine bestehende Verbindung in den Bereich aufnehmen, gibt es grundsätzlich KEINE TRANSACTION. Ihre Verbindung muss automatisch oder manuell in den Transaktionsbereich aufgenommen werden, damit Ihre Befehle an der Transaktion teilnehmen können.

Q6. Ja, Befehle für eine Verbindung, die nicht an einer Transaktion beteiligt ist, werden wie ausgegeben übergeben, obwohl der Code zufällig in einem Transaktionsbereichsblock ausgeführt wurde, der zurückgesetzt wurde. Wenn die Verbindung nicht in den aktuellen Transaktionsbereich aufgenommen wird, nimmt sie nicht an der Transaktion teil. Das Commit oder Rollback der Transaktion wirkt sich also nicht auf Befehle aus, die für eine Verbindung ausgegeben werden, die nicht im Transaktionsbereich aufgeführt ist . Das ist sehr schwer zu erkennen, es sei denn, Sie verstehen den automatischen Eintragsprozess: Er tritt nur auf, wenn eine Verbindung innerhalb eines aktiven Transaktionsbereichs geöffnet wird .

Q7. Ja. Eine bestehende Verbindung kann durch Aufruf von EnlistTransaction (Transaction.Current) explizit in den aktuellen Transaktionsbereich eingetragen werden. Sie können auch eine Verbindung in einem separaten Thread in der Transaktion mithilfe einer DependentTransaction erstellen, aber wie zuvor bin ich mir nicht sicher, wie zwei Verbindungen in derselben Transaktion für die gleiche database interagieren können … und Fehler auftreten können, und Natürlich führt die zweite Verbindung dazu, dass die Transaktion zu einer verteilten Transaktion eskaliert.

Q8. Ein Fehler kann ausgetriggers werden. Wenn TransactionScopeOption.Required verwendet wurde und die Verbindung bereits in einer Transaktionsbereichs-Transaktion eingetragen war, liegt kein Fehler vor. Tatsächlich wird für den Bereich keine neue Transaktion erstellt und die Transaktionsanzahl (@@ trancount) wird nicht erhöht. Wenn Sie jedoch TransactionScopeOption.RequiresNew verwenden, erhalten Sie eine hilfreiche Fehlermeldung beim Versuch, die Verbindung in der neuen Transaktionsbereichs-Transaktion einzutragen: “Verbindung hat derzeit eine Transaktion eingetragen. Beenden Sie die aktuelle Transaktion und versuchen Sie es erneut.” Und ja, wenn Sie die Transaktion abschließen, in der die Verbindung eingetragen ist, können Sie die Verbindung sicher in einer neuen Transaktion eintragen. Update: Wenn Sie zuvor BeginTransaction für die Verbindung aufgerufen haben, wird ein geringfügig anderer Fehler ausgegeben, wenn Sie versuchen, eine neue Transaktionsbereichs-Transaktion einzutragen: “Die Transaktion kann nicht in die Transaktion aufgenommen werden, da auf der Verbindung eine lokale Transaktion ausgeführt wird wiederholen.” Auf der anderen Seite können Sie BeginTransaction für die SqlConnection sicher aufrufen, während sie in einer Transaktionsbereichstransaktion eingetragen ist, und das wird @@ trancount tatsächlich um eins erhöhen, im Gegensatz zur Verwendung der Option Erforderlich eines verschachtelten Transaktionsbereichs, die dies nicht verursacht erhöhen, ansteigen. Wenn Sie dann mit der Option Erforderlich einen weiteren verschachtelten Transaktionsbereich erstellen, erhalten Sie interessanterweise keinen Fehler, da sich nichts ändert, da bereits eine Transaktionstransaktion aktiv ist (denken Sie daran, dass @@ trancount bei einer Transaktion nicht erhöht wird) Bereichstransaktion ist bereits aktiv und die Option Erforderlich wird verwendet).

Q9. Ja. Befehle nehmen an jeder Transaktion teil, in die die Verbindung eingetragen wird, unabhängig davon, was der aktive Transaktionsbereich im C # -Code ist.

Nette Arbeit Triynko, deine Antworten sehen für mich ziemlich genau und vollständig aus. Einige andere Dinge möchte ich hervorheben:

(1) Manuelle Einstellung

In Ihrem obigen Code zeigen Sie (korrekt) eine manuelle Registrierung wie folgt an:

 using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (TransactionScope ts = new TransactionScope()) { conn.EnlistTransaction(Transaction.Current); } } 

Es ist jedoch auch möglich, dies mit Enlist = false in der Verbindungszeichenfolge zu tun.

 string connStr = "...; Enlist = false"; using (TransactionScope ts = new TransactionScope()) { using (SqlConnection conn1 = new SqlConnection(connStr)) { conn1.Open(); conn1.EnlistTransaction(Transaction.Current); } using (SqlConnection conn2 = new SqlConnection(connStr)) { conn2.Open(); conn2.EnlistTransaction(Transaction.Current); } } 

Es gibt noch etwas zu beachten. Wenn conn2 geöffnet wird, weiß der Code des Verbindungspools nicht, dass Sie ihn später in der gleichen Transaktion wie conn1 eintragen wollen, was bedeutet, dass conn2 eine andere interne Verbindung als conn1 erhält. Dann, wenn conn2 eingetragen ist, sind nun 2 Verbindungen eingetragen, so dass die Transaktion zu MSDTC befördert werden muss. Diese Aktion kann nur durch automatische Anmeldung vermieden werden.

(2) Vor der Verwendung von .Net 4.0 empfehle ich dringend, “Transaction Binding = Explicit Unbind” in der Verbindungszeichenfolge zu setzen . Dieses Problem wurde in .Net 4.0 behoben, was Explicit Unbind völlig unnötig macht.

(3) Rolling Ihre eigene CommittableTransaction und Einstellung Transaction.Current dazu ist im Wesentlichen das gleiche wie was TransactionScope tut. Dies ist selten wirklich nützlich, nur zur Info.

(4) Transaction.Current ist Thread-statisch. Dies bedeutet, dass Transaction.Current nur für den Thread festgelegt ist, der das TransactionScope . Daher sind mehrere Threads, die dasselbe TransactionScope (möglicherweise unter Verwendung von Task ) ausführen, nicht möglich.

Eine andere bizarre Situation, die wir gesehen haben, ist, dass, wenn Sie einen EntityConnectionStringBuilder er mit TransactionScope.Current EntityConnectionStringBuilder und (wie wir denken) in die Transaktion eingetragen wird. Wir haben dies im Debugger beobachtet, wo TransactionScope.Current current.TransactionInformation.internalTransaction vor dem Konstruieren enlistmentCount == 1 und danach enlistmentCount == 2 .

Um dies zu vermeiden, konstruiere es innen

using (new TransactionScope(TransactionScopeOption.Suppress))

und möglicherweise außerhalb des scopes Ihrer Operation (wir bauten es jedes Mal, wenn wir eine Verbindung brauchten).