O que significa um SqlConnection ser "inscrito" em uma transação? Significa simplesmente que os comandos executados na conexão participarão da transação?
Em caso afirmativo, em que circunstâncias um SqlConnection é automaticamente inscrito em uma transação TransactionScope ambiente?
Veja as perguntas nos comentários do código. Meu palpite para a resposta de cada pergunta segue cada pergunta entre parênteses.
Cenário 1: Abrindo conexões DENTRO de um escopo de transação
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?)
}
Cenário 2: Usando conexões DENTRO de um escopo de transação que foi aberto FORA dele
//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?)
}
Bom trabalho Triynko, todas as suas respostas parecem bastante precisas e completas para mim. Gostaria de destacar outras coisas:
(1) Alistamento manual
No código acima, você (corretamente) mostra o alistamento manual como este:
No entanto, também é possível fazer assim, usando Enlist = false na cadeia de conexão.
Há outra coisa a observar aqui. Quando o conn2 é aberto, o código do conjunto de conexões não sabe que você deseja incluí-lo posteriormente na mesma transação que o conn1, o que significa que o conn2 recebe uma conexão interna diferente da conn1. Em seguida, quando o conn2 é registrado, agora existem 2 conexões, portanto a transação deve ser promovida para o MSDTC. Esta promoção só pode ser evitada usando o alistamento automático.
(2) Antes do .Net 4.0, eu recomendo a configuração "Ligação de transação = Desvinculação explícita" na cadeia de conexão . Esse problema foi corrigido no .Net 4.0, tornando o Explicit Unbind totalmente desnecessário.
(3) Rolar o seu próprio
CommittableTransaction
e definir oTransaction.Current
que é essencialmente a mesma coisa que o queTransactionScope
faz. Isso raramente é realmente útil, apenas para sua informação.(4)
Transaction.Current
é estático da linha. Isso significa queTransaction.Current
é definido apenas no segmento que criou o arquivoTransactionScope
. Portanto, vários threads executando o mesmoTransactionScope
(possivelmente usandoTask
) não são possíveis.fonte
Uma outra situação bizarra que vimos é que, se você construir uma,
EntityConnectionStringBuilder
ela estragaráTransactionScope.Current
e (pensamos) se alistar na transação. Observamos isso no depurador, ondeTransactionScope.Current
oscurrent.TransactionInformation.internalTransaction
programas são mostradosenlistmentCount == 1
antes da construção eenlistmentCount == 2
depois.Para evitar isso, construa-o dentro
using (new TransactionScope(TransactionScopeOption.Suppress))
e possivelmente fora do escopo de sua operação (estávamos construindo toda vez que precisávamos de uma conexão).
fonte