¿Qué significa que una SqlConnection se "aliste" en una transacción? ¿Significa simplemente que los comandos que ejecuto en la conexión participarán en la transacción?
Si es así, ¿en qué circunstancias se inscribe automáticamente una conexión SqlConnection en una transacción TransactionScope ambiente?
Ver preguntas en los comentarios del código. Mi suposición a la respuesta de cada pregunta sigue a cada pregunta entre paréntesis.
Escenario 1: Abrir conexiones DENTRO de un alcance de transacción
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?)
}
Escenario 2: Uso de conexiones DENTRO de un alcance de transacción que se abrió FUERA de él
//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?)
}
Buen trabajo Triynko, todas tus respuestas me parecen bastante precisas y completas. Algunas otras cosas que me gustaría señalar:
(1) Alistamiento manual
En su código anterior, usted (correctamente) muestra el alistamiento manual de esta manera:
Sin embargo, también es posible hacerlo así, usando Enlist = false en la cadena de conexión.
Hay otra cosa a tener en cuenta aquí. Cuando se abre conn2, el código del grupo de conexiones no sabe que desea enlistarlo más tarde en la misma transacción que conn1, lo que significa que conn2 recibe una conexión interna diferente que conn1. Luego, cuando se enlista conn2, ahora hay 2 conexiones enlistadas, por lo que la transacción debe promoverse a MSDTC. Esta promoción solo se puede evitar utilizando el alistamiento automático.
(2) Antes de .Net 4.0, recomiendo configurar "Vinculación de transacción = desvinculación explícita" en la cadena de conexión . Este problema se soluciona en .Net 4.0, lo que hace que la desconexión explícita sea totalmente innecesaria.
(3) Rodar el tuyo
CommittableTransaction
y configurarloTransaction.Current
es esencialmente lo mismo que lo queTransactionScope
hace. Esto rara vez es realmente útil, solo para su información.(4)
Transaction.Current
es hilo estático. Esto significa queTransaction.Current
solo se establece en el hilo que creó elTransactionScope
. Por lo tanto, varios subprocesos que ejecutan lo mismoTransactionScope
(posiblemente usandoTask
) no es posible.fuente
Otra situación extraña que hemos visto es que si construyes una
EntityConnectionStringBuilder
, será un desastreTransactionScope.Current
y (creemos) se alistará en la transacción. Hemos observado esto en el depurador, dondeTransactionScope.Current
'scurrent.TransactionInformation.internalTransaction
espectáculosenlistmentCount == 1
antes de construir, yenlistmentCount == 2
después.Para evitar esto, constrúyalo adentro
using (new TransactionScope(TransactionScopeOption.Suppress))
y posiblemente fuera del alcance de su operación (la estábamos construyendo cada vez que necesitábamos una conexión).
fuente