Comportamento da transação de serviço
O exemplo Transactions demonstra o uso de uma transação coordenada pelo cliente e as configurações de ServiceBehaviorAttribute e OperationBehaviorAttribute para controlar o comportamento da transação de serviço. Este exemplo é baseado no exemplo de Introdução que implementa um serviço de calculadora, mas é estendido para manter um log de servidor das operações executadas em uma tabela de banco de dados e um total de execução com monitoração de estado para as operações da calculadora. As gravações persistentes na tabela de log do servidor dependem do resultado de uma transação coordenada pelo cliente - se a transação do cliente não for concluída, a transação do serviço Web garantirá que as atualizações do banco de dados não sejam confirmadas.
Nota
O procedimento de instalação e as instruções de compilação para este exemplo estão localizados no final deste artigo.
O contrato para o serviço define que todas as operações exigem que uma transação seja fluída com solicitações:
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples",
SessionMode = SessionMode.Required)]
public interface ICalculator
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Add(double n);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Subtract(double n);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Multiply(double n);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Divide(double n);
}
Para habilitar o fluxo de transações de entrada, o serviço é configurado com o wsHttpBinding fornecido pelo sistema com o atributo transactionFlow definido como true
. Essa associação usa o protocolo interoperável WSAtomicTransactionOctober2004:
<bindings>
<wsHttpBinding>
<binding name="transactionalBinding" transactionFlow="true" />
</wsHttpBinding>
</bindings>
Depois de iniciar uma conexão com o serviço e uma transação, o cliente acessa várias operações de serviço dentro do escopo dessa transação e, em seguida, conclui a transação e fecha a conexão:
// Create a client
CalculatorClient client = new CalculatorClient();
// Create a transaction scope with the default isolation level of Serializable
using (TransactionScope tx = new TransactionScope())
{
Console.WriteLine("Starting transaction");
// Call the Add service operation.
double value = 100.00D;
Console.WriteLine(" Adding {0}, running total={1}",
value, client.Add(value));
// Call the Subtract service operation.
value = 45.00D;
Console.WriteLine(" Subtracting {0}, running total={1}",
value, client.Subtract(value));
// Call the Multiply service operation.
value = 9.00D;
Console.WriteLine(" Multiplying by {0}, running total={1}",
value, client.Multiply(value));
// Call the Divide service operation.
value = 15.00D;
Console.WriteLine(" Dividing by {0}, running total={1}",
value, client.Divide(value));
Console.WriteLine(" Completing transaction");
tx.Complete();
}
Console.WriteLine("Transaction committed");
// Closing the client gracefully closes the connection and cleans up resources
client.Close();
No serviço, há três atributos que afetam o comportamento da transação de serviço, e eles fazem isso das seguintes maneiras:
Sobre o
ServiceBehaviorAttribute
:A
TransactionTimeout
propriedade especifica o período de tempo dentro do qual uma transação deve ser concluída. Neste exemplo, ele é definido como 30 segundos.A
TransactionIsolationLevel
propriedade especifica o nível de isolamento que o serviço suporta. Isso é necessário para corresponder ao nível de isolamento do cliente.A
ReleaseServiceInstanceOnTransactionComplete
propriedade especifica se a instância de serviço é reciclada quando uma transação é concluída. Ao defini-lo comofalse
, o serviço mantém a mesma instância de serviço nas solicitações de operação. Isso é necessário para manter o total em execução. Se definido comotrue
, uma nova instância é gerada após cada ação concluída.A
TransactionAutoCompleteOnSessionClose
propriedade especifica se as transações pendentes são concluídas quando a sessão é encerrada. Ao defini-lo comofalse
, as operações individuais são necessárias para definir a OperationBehaviorAttribute.TransactionAutoComplete propriedade comotrue
ou para exigir explicitamente uma chamada para o OperationContext.SetTransactionComplete() método para concluir transações. Este exemplo demonstra ambas as abordagens.
Sobre o
ServiceContractAttribute
:- A
SessionMode
propriedade especifica se o serviço correlaciona as solicitações apropriadas em uma sessão lógica. Como esse serviço inclui operações em que a propriedade OperationBehaviorAttribute TransactionAutoComplete está definida comofalse
(Multiply and Divide),SessionMode.Required
deve ser especificada. Por exemplo, a operação Multiply não conclui sua transação e, em vez disso, depende de uma chamada posterior para Dividir para concluir usando oSetTransactionComplete
método, o serviço deve ser capaz de determinar que essas operações estão ocorrendo dentro da mesma sessão.
- A
Sobre o
OperationBehaviorAttribute
:A
TransactionScopeRequired
propriedade especifica se as ações da operação devem ser executadas dentro de um escopo de transação. Isso é definido paratrue
todas as operações neste exemplo e, como o cliente flui sua transação para todas as operações, as ações ocorrem dentro do escopo dessa transação do cliente.A
TransactionAutoComplete
propriedade especifica se a transação na qual o método é executado é concluída automaticamente se não ocorrerem exceções sem tratamento. Conforme descrito anteriormente, isso é definido paratrue
as operações Adicionar e Subtrair, masfalse
para as operações Multiplicar e Dividir. As operações Adicionar e Subtrair concluem suas ações automaticamente, a Divisão completa suas ações por meio de uma chamada explícita para oSetTransactionComplete
método e a Multiplicação não conclui suas ações, mas depende e requer uma chamada posterior, como Dividir, para concluir as ações.
A implementação do serviço atribuído é a seguinte:
[ServiceBehavior(
TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable,
TransactionTimeout = "00:00:30",
ReleaseServiceInstanceOnTransactionComplete = false,
TransactionAutoCompleteOnSessionClose = false)]
public class CalculatorService : ICalculator
{
double runningTotal;
public CalculatorService()
{
Console.WriteLine("Creating new service instance...");
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public double Add(double n)
{
RecordToLog(String.Format(CultureInfo.CurrentCulture, "Adding {0} to {1}", n, runningTotal));
runningTotal = runningTotal + n;
return runningTotal;
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public double Subtract(double n)
{
RecordToLog(String.Format(CultureInfo.CurrentCulture, "Subtracting {0} from {1}", n, runningTotal));
runningTotal = runningTotal - n;
return runningTotal;
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public double Multiply(double n)
{
RecordToLog(String.Format(CultureInfo.CurrentCulture, "Multiplying {0} by {1}", runningTotal, n));
runningTotal = runningTotal * n;
return runningTotal;
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public double Divide(double n)
{
RecordToLog(String.Format(CultureInfo.CurrentCulture, "Dividing {0} by {1}", runningTotal, n));
runningTotal = runningTotal / n;
OperationContext.Current.SetTransactionComplete();
return runningTotal;
}
// Logging method omitted for brevity
}
Quando você executa o exemplo, as solicitações de operação e as respostas são exibidas na janela do console do cliente. Pressione ENTER na janela do cliente para desligar o cliente.
Starting transaction
Performing calculations...
Adding 100, running total=100
Subtracting 45, running total=55
Multiplying by 9, running total=495
Dividing by 15, running total=33
Completing transaction
Transaction committed
Press <ENTER> to terminate client.
O registro das solicitações de operação de serviço é exibido na janela do console do serviço. Pressione ENTER na janela do cliente para desligar o cliente.
Press <ENTER> to terminate service.
Creating new service instance...
Writing row 1 to database: Adding 100 to 0
Writing row 2 to database: Subtracting 45 from 100
Writing row 3 to database: Multiplying 55 by 9
Writing row 4 to database: Dividing 495 by 15
Observe que, além de manter o total em execução dos cálculos, o serviço relata a criação de instâncias (uma instância para este exemplo) e registra as solicitações de operação em um banco de dados. Como todas as solicitações fluem na transação do cliente, qualquer falha na conclusão dessa transação resulta na reversão de todas as operações do banco de dados. Isto pode ser demonstrado de várias formas:
Comente a chamada do cliente para
tx.Complete
() e execute novamente - isso resulta no cliente saindo do escopo da transação sem concluir sua transação.Comente a chamada para a operação de serviço Dividir - isso impede que a ação iniciada pela operação Multiplicar seja concluída e, portanto, a transação do cliente também não seja concluída.
Lance uma exceção não tratada em qualquer lugar no escopo da transação do cliente - isso também impede que o cliente conclua sua transação.
O resultado de qualquer um deles é que nenhuma das operações executadas dentro desse escopo é confirmada e a contagem de linhas persistidas no banco de dados não é incrementada.
Nota
Como parte do processo de compilação, o arquivo de banco de dados é copiado para a pasta bin. Você deve examinar essa cópia do arquivo de banco de dados para observar as linhas que são persistentes para o log em vez do arquivo que está incluído no projeto do Visual Studio.
Para configurar, compilar e executar o exemplo
Verifique se você instalou o SQL Server 2005 Express Edition ou o SQL Server 2005. No arquivo App.config do serviço, o banco de dados
connectionString
pode ser definido ou as interações do banco de dados podem ser desabilitadas definindo o valor appSettingsusingSql
comofalse
.Para criar a edição C# ou Visual Basic .NET da solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.
Para executar o exemplo em uma configuração de máquina única ou cruzada, siga as instruções em Executando os exemplos do Windows Communication Foundation.
Se você executar o exemplo entre máquinas, deverá configurar o Microsoft Distributed Transaction Coordinator (MSDTC) para habilitar o fluxo de transações de rede e usar a ferramenta WsatConfig.exe para habilitar o suporte à rede de transações do Windows Communication Foundation (WCF).
Para configurar o Microsoft Distributed Transaction Coordinator (MSDTC) para dar suporte à execução do exemplo em máquinas
Na máquina de serviço, configure o MSDTC para permitir transações de rede de entrada.
No menu Iniciar, navegue até Painel de Controle, Ferramentas Administrativas e Serviços de Componentes.
Clique com o botão direito do mouse em Meu computador e selecione Propriedades.
Na guia MSDTC, clique em Configuração de Segurança.
Verifique Acesso DTC à rede e Permitir entrada.
Clique em Sim para reiniciar o serviço MS DTC e, em seguida, clique em OK.
Clique em OK para fechar a caixa de diálogo.
Na máquina de serviço e na máquina cliente, configure o Firewall do Windows para incluir o Microsoft Distributed Transaction Coordinator (MSDTC) na lista de aplicativos excluídos:
Execute a aplicação Firewall do Windows a partir do Painel de Controlo.
Na guia Exceções, clique em Adicionar programa.
Navegue até a pasta C:\WINDOWS\System32.
Selecione Msdtc.exe e clique em Abrir.
Clique em OK para fechar a caixa de diálogo Adicionar Programa e clique em OK novamente para fechar o miniaplicativo Firewall do Windows.
Na máquina cliente, configure o MSDTC para permitir transações de rede de saída:
No menu Iniciar, navegue até Painel de Controle, Ferramentas Administrativas e Serviços de Componentes.
Clique com o botão direito do mouse em Meu computador e selecione Propriedades.
Na guia MSDTC, clique em Configuração de Segurança.
Marque Acesso DTC à Rede e Permitir Saída.
Clique em Sim para reiniciar o serviço MS DTC e, em seguida, clique em OK.
Clique em OK para fechar a caixa de diálogo.