Dans quelles circonstances une SqlConnection est-elle automatiquement inscrite dans une TransactionScope Transaction ambiante?

Qu’est-ce que cela signifie pour une SqlConnection d’être “enrôlée” dans une transaction? Est-ce que cela signifie simplement que les commandes que j’exécute sur la connexion participeront à la transaction?

Si oui, dans quelles circonstances une SqlConnection est-elle automatiquement inscrite dans une transaction TransactionScope ambiante?

Voir les questions dans les commentaires de code. Je suppose que la réponse de chaque question suit chaque question entre parenthèses.

Scénario 1: Ouverture de connexions à l’intérieur d’une étendue de transaction

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 ssortingng, // 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 dissortingbuted transaction? (Yes?) } 

Scénario 2: Utilisation des connexions à l’intérieur d’une étendue de transaction ouverte en dehors de celle-ci

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

    J’ai fait des tests depuis que j’ai posé cette question et j’ai trouvé la plupart des réponses, sinon toutes, car personne d’autre n’a répondu. S’il vous plaît laissez-moi savoir si j’ai manqué quelque chose.

    Q1. Oui, sauf si “enlist = false” est spécifié dans la chaîne de connexion. Le pool de connexions trouve une connexion utilisable. Une connexion utilisable est une connexion qui n’est pas inscrite dans une transaction ou qui est inscrite dans la même transaction.

    Q2. La deuxième connexion est une connexion indépendante, qui participe à la même transaction. Je ne suis pas sûr de l’interaction des commandes sur ces deux connexions, car elles s’exécutent sur la même firebase database, mais je pense que des erreurs peuvent survenir si des commandes sont émises simultanément: erreurs telles que “contexte de transaction utilisé par une autre session ”

    Q3. Oui, elle est transmise à une transaction dissortingbuée, si bien que l’inscription de plusieurs connexions, même avec la même chaîne de connexion, provoque une transaction dissortingbuée, ce qui peut être confirmé en recherchant un GUID non nul sur Transaction.Current.TransactionInformation .DissortingbutedIdentifier. * Mise à jour: j’ai lu quelque part que cela était corrigé dans SQL Server 2008, afin que MSDTC ne soit pas utilisé lorsque la même chaîne de connexion est utilisée pour les deux connexions (tant que les deux connexions ne sont pas ouvertes en même temps). Cela vous permet d’ouvrir une connexion et de la fermer plusieurs fois dans une transaction, ce qui pourrait permettre de mieux utiliser le pool de connexions en ouvrant les connexions le plus tard possible et en les fermant dès que possible.

    Q4. Non. Une connexion ouverte lorsqu’aucune étendue de transaction n’était active ne sera automatiquement inscrite dans une nouvelle étendue de transaction.

    Q5. Non. À moins d’ouvrir une connexion dans la scope de la transaction ou d’inscrire une connexion existante dans le périmètre, il n’ya AUCUNE TRANSACTION. Votre connexion doit être automatiquement ou manuellement inscrite dans la scope de la transaction pour que vos commandes puissent participer à la transaction.

    Q6. Oui, les commandes sur une connexion ne participant pas à une transaction sont validées telles qu’elles sont émises, même si le code est exécuté dans un bloc de scope de transaction qui a été annulé. Si la connexion n’est pas inscrite dans la scope de la transaction en cours, elle ne participe pas à la transaction, donc la validation ou l’annulation de la transaction n’aura aucun effet sur les commandes émises sur une connexion non inscrite dans la scope de la transaction . C’est très difficile à identifier, à moins que vous ne compreniez le processus d’inscription automatique: il se produit uniquement lorsqu’une connexion est ouverte dans une étendue de transaction active.

    Q7. Oui. Une connexion existante peut être explicitement inscrite dans l’étendue de la transaction en cours en appelant EnlistTransaction (Transaction.Current). Vous pouvez également inscrire une connexion sur un thread distinct dans la transaction en utilisant une DependentTransaction, mais comme précédemment, je ne suis pas sûr de savoir comment deux connexions impliquées dans la même transaction peuvent interagir avec la même firebase database. Bien entendu, la deuxième connexion enrôlée provoque l’escalade de la transaction vers une transaction dissortingbuée.

    Q8. Une erreur peut être lancée. Si TransactionScopeOption.Required a été utilisé et que la connexion a déjà été inscrite dans une transaction de scope de transaction, il n’y a pas d’erreur. En fait, aucune nouvelle transaction n’a été créée pour l’étendue et le nombre de transactions (@@ trancount) n’augmente pas. Si, toutefois, vous utilisez TransactionScopeOption.RequiresNew, vous obtenez un message d’erreur utile lors de la tentative d’inscription de la connexion dans la nouvelle transaction: “Connection a actuellement une transaction inscrite. Terminez la transaction en cours et réessayez”. Et oui, si vous terminez la transaction dans laquelle la connexion est inscrite, vous pouvez enregistrer la connexion en toute sécurité dans une nouvelle transaction. Mise à jour: Si vous avez précédemment appelé BeginTransaction sur la connexion, une erreur légèrement différente est générée lorsque vous essayez de vous inscrire dans une nouvelle transaction de scope de transaction: “Impossible d’inscrire dans la transaction car une transaction locale est en cours sur la connexion. recommencez.” D’un autre côté, vous pouvez appeler en toute sécurité BeginTransaction sur SqlConnection pendant son enregistrement dans une transaction de scope de transaction, et cela augmentera réellement @@ trancount de un, contrairement à l’utilisation de l’option Required d’une étendue de transaction nestede. augmenter. Fait intéressant, si vous continuez à créer une autre étendue de transaction nestede avec l’option Obligatoire, vous n’obtiendrez aucune erreur, car rien ne change à la suite d’une transaction de scope de transaction active (n’attendez pas transaction de scope est déjà active et l’option Obligatoire est utilisée).

    Q9. Oui. Les commandes participent à la transaction dans laquelle la connexion est inscrite, quelle que soit la scope de la transaction active dans le code C #.

    Beau travail Triynko, vos réponses me paraissent bien précises et complètes. Quelques autres choses que j’aimerais souligner:

    (1) enrôlement manuel

    Dans votre code ci-dessus, vous montrez (correctement) l’inscription manuelle comme ceci:

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

    Cependant, il est également possible de le faire comme ceci, en utilisant Enlist = false dans la chaîne de connexion.

     ssortingng 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); } } 

    Il y a une autre chose à noter ici. Lorsque conn2 est ouvert, le code du pool de connexion ne sait pas que vous souhaitez l’enrôler ultérieurement dans la même transaction que conn1, ce qui signifie que conn2 se voit atsortingbuer une connexion interne différente de conn1. Ensuite, lorsque conn2 est enrôlé, il y a maintenant 2 connexions inscrites, de sorte que la transaction doit être promue au format MSDTC. Cette promotion ne peut être évitée qu’en utilisant l’enrôlement automatique.

    (2) Avant .Net 4.0, je recommande fortement de définir “Liaison de transaction = Dénouement explicite” dans la chaîne de connexion . Ce problème est résolu dans .Net 4.0, rendant le lien explicite totalement inutile.

    (3) Rouler votre propre CommittableTransaction et définir Transaction.Current à cela est essentiellement la même chose que TransactionScope . Ceci est rarement utile, juste pour info.

    (4) Transaction.Current est thread-static. Cela signifie que Transaction.Current est uniquement défini sur le thread qui a créé TransactionScope . Ainsi, plusieurs threads exécutant le même TransactionScope (éventuellement en utilisant Task ) ne sont pas possibles.

    Une autre situation bizarre que nous avons vue est que si vous construisez un EntityConnectionSsortingngBuilder il se EntityConnectionSsortingngBuilder avec TransactionScope.Current et (nous pensons) s’inscrire dans la transaction. Nous avons observé cela dans le débogueur, où le current.TransactionInformation.internalTransaction TransactionScope.Current affiche enlistmentCount == 1 avant la construction et enlistmentCount == 2 par la suite.

    Pour éviter cela, construisez-le à l’intérieur

    using (new TransactionScope(TransactionScopeOption.Suppress))

    et peut-être hors de la scope de vos opérations (nous le construisions chaque fois que nous avions besoin d’une connexion).