web-dev-qa-db-fra.com

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

Qu'est-ce que cela signifie pour un SqlConnection d'être "enrôlé" dans une transaction? Cela signifie-t-il simplement que les commandes que j'exécute sur la connexion participeront à la transaction?

Si tel est le cas, dans quelles circonstances un SqlConnection automatiquement est-il inscrit dans une transaction TransactionScope ambiante?

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

Scénario 1: Ouverture de connexions DANS 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 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?)
}

Scénario 2: Utilisation de connexions dans 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?)
}
198
Triynko

J'ai fait quelques tests depuis que j'ai posé cette question et j'ai trouvé la plupart, sinon toutes les réponses, tout seul, puisque 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 une connexion 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 base de données, mais je pense que des erreurs peuvent se produire si des commandes sont émises simultanément: des erreurs telles que "Transaction"). contexte utilisé par une autre session "

Q3. Oui, la transaction est transférée à une transaction distribuée. Par conséquent, si plusieurs connexions sont insérées, même avec la même chaîne de connexion, elle devient une transaction distribuée. transaction, qui peut être confirmée en recherchant un non-null GUID à Transaction.Current.TransactionInformation.DistributedIdentifier. * Mise à jour: j'ai lu quelque part que cela est résolu dans SQL Server 2008, de sorte que MSDTC n'est 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 le plus tôt possible.

Q4. Non. Une connexion ouverte quand aucune étendue de transaction n'était active ne sera pas automatiquement inscrite dans une étendue de transaction nouvellement créée.

Q5. Non. Sauf si vous ouvrez une connexion dans l'étendue de la transaction ou inscrivez une connexion existante dans l'étendue, NO TRANSACTION est en fait. Votre connexion doit être automatiquement ou manuellement inscrite dans la portée de la transaction pour que vos commandes puissent participer à la transaction.

Q6. Oui, les commandes d’une connexion ne participant à aucune transaction sont validées telles qu’elles ont été émises, même si le code s’est exécuté dans un bloc d’étendue de la transaction qui: a été annulée. Si la connexion n'est pas inscrite dans l'étendue de la transaction en cours, elle ne participe pas à la transaction. Par conséquent, la validation ou l'annulation de la transaction n'aura aucun effet sur les commandes émises sur une connexion non inscrite dans l'étendue de la transaction ... as ce gars a découvert . C'est très difficile à repérer à moins de comprendre le processus d'inscription automatique: il ne se produit que lorsqu'une connexion est ouverte à l'intérieur une étendue de transaction active.

Q7. Oui. Une connexion existante peut être explicitement inscrite dans la portée de la transaction en cours en appelant EnlistTransaction (Transaction.Current). Vous pouvez également inscrire une connexion sur un thread distinct de la transaction en utilisant un DependentTransaction, mais comme avant, je ne suis pas sûr de savoir comment deux connexions impliquées dans la même transaction avec la même base de données peuvent interagir ... et des erreurs peuvent se produire, et bien entendu, la deuxième connexion répertoriée entraîne la transformation de la transaction en transaction distribuée.

Q8. Une erreur peut être générée. Si TransactionScopeOption.Required a été utilisé et que la connexion était déjà inscrite dans une transaction d'étendue de transaction, il n'y a pas d'erreur; En fait, aucune nouvelle transaction n'est 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 lorsque vous essayez d'inscrire la connexion dans la nouvelle transaction d'étendue de transaction: "La transaction est actuellement inscrite dans la transaction. Terminez la transaction en cours, puis réessayez." Et oui, si vous terminez la transaction dans laquelle la connexion est inscrite, vous pouvez l'inscrire 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 d’inscrire une nouvelle transaction d’étendue de transaction: "Impossible de s’inscrire dans la transaction car une transaction locale est en cours sur la connexion. Terminez la transaction locale et réessayez. " D'autre part, vous pouvez appeler BeginTransaction en toute sécurité sur SqlConnection tant qu'il est inscrit dans une transaction d'étendue de transaction, ce qui augmentera de @@ trancount, contrairement à l'option Required d'une étendue de transaction imbriquée. augmenter. Il est intéressant de noter que si vous créez ensuite une autre étendue de transaction imbriquée avec l’option Required, vous n’obtiendrez pas d’erreur, car rien ne change du fait qu’une transaction de portée de transaction est déjà active (rappelez-vous que @@ trancount n’est pas augmenté lorsqu’une transaction La transaction d'étendue est déjà active et l'option Required est utilisée).

Q9. Oui. Les commandes participent à la transaction à laquelle la connexion est associée, quelle que soit la portée de la transaction active dans le code C #.

182
Triynko

Beau travail Triynko, tes réponses me semblent assez précises et complètes. Quelques autres choses que je voudrais souligner:

(1) Inscription manuelle

Dans votre code ci-dessus, vous montrez (correctement) l'enrôlement manuel 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.

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

Il y a une autre chose à noter ici. Lorsque conn2 est ouvert, le code du pool de connexions ne sait pas que vous souhaitez l'inscrire ultérieurement dans la même transaction que conn1, ce qui signifie que conn2 dispose d'une connexion interne différente de celle de conn1. Ensuite, lorsque conn2 est inscrit, il y a maintenant 2 connexions inscrites et la transaction doit donc ê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 vivement de configurer "Transaction Binding = Explicit Unbind" dans la chaîne de connexion . Ce problème est résolu dans .Net 4.0, rendant Explicit Unbind totalement inutile.

(3) Lancez votre propre CommittableTransaction et définissez Transaction.Current à cela est essentiellement la même chose que ce que TransactionScope fait. C'est rarement utile, juste pour votre information.

(4) Transaction.Current est thread-statique. Cela signifie que Transaction.Current n'est défini que sur le fil qui a créé le TransactionScope. Donc, plusieurs threads exécutant le même TransactionScope (éventuellement avec Task) ne sont pas possibles.

19
Jared Moore

Une autre situation bizarre que nous avons rencontrée est que si vous construisez un EntityConnectionStringBuilder, il va se faufiler avec TransactionScope.Current Et (nous pensons) s’inscrire dans la transaction. Nous l'avons observé dans le débogueur, où TransactionScope.Current De current.TransactionInformation.internalTransaction Indique enlistmentCount == 1 Avant de construire, et enlistmentCount == 2 Après.

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

using (new TransactionScope(TransactionScopeOption.Suppress))

et éventuellement hors de portée de votre opération (nous la construisions chaque fois que nous avions besoin d'une connexion).

0
Todd