Em nosso projeto, estamos usando o TransactionScope para garantir que nossa camada de acesso a dados execute suas ações em uma transação. Nosso objetivo é não exigir que o serviço MSDTC seja ativado nas máquinas de nossos usuários finais.
O problema é que, na metade das máquinas de nossos desenvolvedores, podemos executar com o MSDTC desativado. A outra metade deve estar ativada ou a mensagem de erro "MSDTC no [SERVIDOR] está indisponível" .
Isso realmente me fez coçar a cabeça e me levou a pensar seriamente em voltar para uma solução semelhante ao TransactionScope, baseada em objetos de transação do ADO.NET. É aparentemente insano - o mesmo código que funciona (e não escalar) na metade do nosso desenvolvedor é faz escalar do outro desenvolvedor do.
Eu estava esperando uma resposta melhor para o Trace porque uma transação é escalada para o DTC, mas infelizmente não.
Aqui está um exemplo de código que causará o problema: nas máquinas que tentam escalar, ele tenta escalar na segunda conexão.Open () (e sim, não há outra conexão aberta no momento).
using (TransactionScope transactionScope = new TransactionScope() {
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open();
using (SqlDataReader reader = command.ExecuteReader()) {
// use the reader
connection.Close();
}
}
}
// Do other stuff here that may or may not involve enlisting
// in the ambient transaction
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open(); // Throws "MSDTC on [SERVER] is unavailable" on some...
// gets here on only half of the developer machines.
}
connection.Close();
}
transactionScope.Complete();
}
Nós realmente descobrimos e tentamos descobrir isso. Aqui estão algumas informações sobre as máquinas nas quais ele funciona:
- Desenvolvedor 1: Windows 7 x64 SQL2008
- Desenvolvedor 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Desenvolvedores nos quais não trabalha:
- Dev 4: 7 x64 do Windows,
SQL2008SQL2005 - Desenvolvedor 5: Windows Vista x86, SQL2005
- Desenvolvedor 6: Windows XP X86, SQL2005
- Meu PC doméstico: Windows Vista Home Premium, x86, SQL2005
Devo acrescentar que todas as máquinas, em um esforço para caçar o problema, foram totalmente corrigidas com tudo o que está disponível no Microsoft Update.
Atualização 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ descreve um problema semelhante ... em 2006!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - leia esse exemplo de código, ele demonstra claramente uma conexão aninhada em segundo (para um segundo servidor SQL, na verdade) que será encaminhado para o DTC. Não estamos fazendo isso em nosso código - não estamos usando servidores SQL diferentes, nem cadeias de conexão diferentes, nem abrimos conexões secundárias - não deve haver escalação para o DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (de 2005) fala sobre como a escalação para o DTC sempre acontece quando se conecta ao SQL2000. Estamos usando o SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN na escalação de transações.
Essa página de escalonamento de transações do MSDN indica que as seguintes condições farão com que uma transação seja escalada para o DTC:
- Pelo menos um recurso durável que não suporta notificações monofásicas é alistado na transação.
- Pelo menos dois recursos duráveis que suportam notificações monofásicas são alistados na transação. Por exemplo, inscrever uma única conexão com não faz com que uma transação seja promovida. No entanto, sempre que você abre uma segunda conexão com um banco de dados, o infra-estrutura System.Transactions detecta que ele é o segundo recurso durável da transação e o encaminha para uma transação MSDTC.
- Uma solicitação para "empacotar" a transação para um domínio de aplicativo ou processo diferente é invocada. Por exemplo, a serialização do objeto de transação através de um limite do domínio do aplicativo. O objeto de transação é empacotado por valor, o que significa que qualquer tentativa de passar por um limite de domínio de aplicativo (mesmo no mesmo processo) resulta em serialização do objeto de transação. Você pode passar os objetos de transação fazendo uma chamada em um método remoto que usa uma transação como parâmetro ou pode tentar acessar um componente de serviço transacional remoto. Isso serializa o objeto de transação e resulta em uma escalação, como quando uma transação é serializada em um domínio de aplicativo. Ele está sendo distribuído e o gerenciador de transações local não é mais adequado.
Não estamos enfrentando o # 3. O número 2 não está acontecendo porque existe apenas uma conexão por vez e também para um único 'recurso durável'. Existe alguma maneira de # 1 estar acontecendo? Alguma configuração do SQL2005 / 8 que faz com que ele não ofereça suporte a notificações monofásicas?
Atualização 2:
Investigamos pessoalmente, pessoalmente, as versões de todos os servidores do SQL Server - "Dev 3" na verdade tem o SQL2008 e "Dev 4" na verdade é o SQL2005. Isso me ensinará a nunca mais confiar em meus colegas de trabalho. ;) Devido a essa alteração nos dados, tenho certeza que encontramos o nosso problema. Nossos desenvolvedores do SQL2008 não estavam enfrentando o problema, porque o SQL2008 possui muitas cópias incríveis incluídas, que o SQL2005 não possui.
Ele também me diz que, como daremos suporte ao SQL2005, não podemos usar o TransactionScope como antes e, se quisermos usar o TransactionScope, precisaremos passar um único objeto SqlConnection por aí ... o que parece problemático em situações em que o SqlConnection não pode ser facilmente contornado ... apenas cheira a instância global-SqlConnection. Pew!
Atualização 3
Apenas para esclarecer aqui na pergunta:
SQL2008:
- Permite várias conexões em um único TransactionScope (como demonstrado no código de exemplo acima).
- Advertência nº 1: se essas várias SqlConnections estiverem aninhadas, ou seja, duas ou mais SqlConnections forem abertas ao mesmo tempo, o TransactionScope será escalado imediatamente para o DTC.
- Advertência # 2: Se um SqlConnection adicional for aberto para um 'recurso durável' diferente (ou seja: um SQL Server diferente), ele será escalado imediatamente para o DTC
SQL2005:
- Não permite várias conexões em um único TransactionScope, ponto final. Ele será escalado quando / se um segundo SqlConnection for aberto.
Atualização 4
No interesse de tornar essa questão ainda mais uma bagunça útil, e apenas por uma questão de clareza, veja como você pode fazer o SQL2005 escalar para o DTC com um único SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Isso só me parece ruim, mas acho que consigo entender se todas as chamadas SqlConnection.Open()
são recebidas do pool de conexões.
"Por que isso pode acontecer?" Bem, se você usar um SqlTableAdapter nessa conexão antes de ser aberto, o SqlTableAdapter abrirá e fechará a conexão, finalizando efetivamente a transação para você, porque agora você não pode reabri-la.
Portanto, basicamente, para usar com êxito o TransactionScope com o SQL2005, é necessário ter algum tipo de objeto de conexão global que permaneça aberto desde o primeiro momento em que o TransactionScope é instanciado até que não seja mais necessário. Além do cheiro de código de um objeto de conexão global, abrir a conexão primeiro e fechá-la por último está em desacordo com a lógica de abrir uma conexão o mais tarde possível e fechá-la o mais rápido possível.