Como: Criar um serviço transacional
Este exemplo demonstra vários aspetos da criação de um serviço transacional e o uso de uma transação iniciada pelo cliente para coordenar operações de serviço.
Criando um serviço transacional
Crie um contrato de serviço e anote as operações com a configuração desejada na TransactionFlowOption enumeração para especificar os requisitos de transação de entrada. Observe que você também pode colocar o TransactionFlowAttribute na classe de serviço que está sendo implementada. Isso permite uma única implementação de uma interface para usar essas configurações de transação, em vez de todas as implementações.
[ServiceContract] public interface ICalculator { [OperationContract] // Use this to require an incoming transaction [TransactionFlow(TransactionFlowOption.Mandatory)] double Add(double n1, double n2); [OperationContract] // Use this to permit an incoming transaction [TransactionFlow(TransactionFlowOption.Allowed)] double Subtract(double n1, double n2); }
Crie uma classe de implementação e use o ServiceBehaviorAttribute para especificar opcionalmente a TransactionIsolationLevel e a TransactionTimeout. Você deve observar que, em muitos casos, o padrão TransactionTimeout de 60 segundos e o padrão TransactionIsolationLevel de
Unspecified
são apropriados. Para cada operação, você pode usar o OperationBehaviorAttribute atributo para especificar se o trabalho executado dentro do método deve ocorrer dentro do escopo de uma transação de acordo com o valor do TransactionScopeRequired atributo. Nesse caso, a transação usada para oAdd
método é a mesma que a transação de entrada obrigatória que é fluída do cliente, e a transação usada para oSubtract
método é a mesma que a transação de entrada, se uma foi fluída do cliente, ou uma nova transação criada implícita e localmente.[ServiceBehavior( TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable, TransactionTimeout = "00:00:45")] public class CalculatorService : ICalculator { [OperationBehavior(TransactionScopeRequired = true)] public double Add(double n1, double n2) { // Perform transactional operation RecordToLog($"Adding {n1} to {n2}"); return n1 + n2; } [OperationBehavior(TransactionScopeRequired = true)] public double Subtract(double n1, double n2) { // Perform transactional operation RecordToLog($"Subtracting {n2} from {n1}"); return n1 - n2; } private static void RecordToLog(string recordText) { // Database operations omitted for brevity // This is where the transaction provides specific benefit // - changes to the database will be committed only when // the transaction completes. } }
Configure as associações no arquivo de configuração, especificando que o contexto da transação deve ser fluído e os protocolos a serem usados para isso. Para obter mais informações, consulte Configuração de transação ServiceModel. Especificamente, o tipo de associação é especificado no atributo do
binding
elemento de ponto de extremidade. O <elemento endpoint> contém umbindingConfiguration
atributo que faz referência a uma configuração de vinculação chamadatransactionalOleTransactionsTcpBinding
, conforme mostrado na configuração de exemplo a seguir.<service name="CalculatorService"> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
O fluxo de transação é habilitado no nível de configuração usando o
transactionFlow
atributo e o protocolo de transação é especificado usando otransactionProtocol
atributo, conforme mostrado na configuração a seguir.<bindings> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Suporte a vários protocolos de transação
Para um desempenho ideal, você deve usar o protocolo OleTransactions para cenários que envolvem um cliente e um serviço escritos usando o Windows Communication Foundation (WCF). No entanto, o protocolo WS-AtomicTransaction (WS-AT) é útil para cenários em que a interoperabilidade com pilhas de protocolos de terceiros é necessária. Você pode configurar os serviços WCF para aceitar ambos os protocolos fornecendo vários pontos de extremidade com associações específicas de protocolo apropriadas, conforme mostrado na configuração de exemplo a seguir.
<service name="CalculatorService"> <endpoint address="http://localhost:8000/CalcService" binding="wsHttpBinding" bindingConfiguration="transactionalWsatHttpBinding" contract="ICalculator" name="WSAtomicTransaction_endpoint" /> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
O protocolo de transação é especificado usando o
transactionProtocol
atributo. No entanto, esse atributo está ausente do fornecido pelo sistemawsHttpBinding
, porque essa associação só pode usar o protocolo WS-AT.<bindings> <wsHttpBinding> <binding name="transactionalWsatHttpBinding" transactionFlow="true" /> </wsHttpBinding> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Controlar a conclusão de uma transação
Por padrão, as operações WCF concluem automaticamente as transações se nenhuma exceção não tratada for lançada. Você pode modificar esse comportamento usando a TransactionAutoComplete propriedade e o SetTransactionComplete método. Quando uma operação é necessária para ocorrer dentro da mesma transação que outra operação (por exemplo, uma operação de débito e crédito), você pode desativar o comportamento de preenchimento automático definindo a TransactionAutoComplete propriedade como
false
mostrado no exemplo de operação a seguirDebit
. A transação que aDebit
operação usa não é concluída até que um método com a TransactionAutoComplete propriedade definida comotrue
seja chamado, conforme mostrado na operaçãoCredit1
, ou quando o SetTransactionComplete método é chamado para marcar explicitamente a transação como concluída, conforme mostrado na operaçãoCredit2
. Note-se que as duas operações de crédito são mostradas para fins ilustrativos, e que uma única operação de crédito seria mais típica.[ServiceBehavior] public class CalculatorService : IAccount { [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = false)] public void Debit(double n) { // Perform debit operation return; } [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = true)] public void Credit1(double n) { // Perform credit operation return; } [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = false)] public void Credit2(double n) { // Perform alternate credit operation OperationContext.Current.SetTransactionComplete(); return; } }
Para fins de correlação de transação, definir a TransactionAutoComplete propriedade como
false
requer o uso de uma associação sessionful. Este requisito é especificado com aSessionMode
propriedade no ServiceContractAttribute.[ServiceContract(SessionMode = SessionMode.Required)] public interface IAccount { [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Debit(double n); [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Credit1(double n); [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Credit2(double n); }
Controlando o tempo de vida de uma instância de serviço transacional
O WCF usa a ReleaseServiceInstanceOnTransactionComplete propriedade para especificar se a instância de serviço subjacente é liberada quando uma transação é concluída. Como esse padrão é , a
true
menos que configurado de outra forma, o WCF exibe um comportamento de ativação "just-in-time" eficiente e previsível. As chamadas para um serviço em uma transação subsequente recebem uma nova instância de serviço sem resquícios do estado da transação anterior. Embora isso geralmente seja útil, às vezes você pode querer manter o estado dentro da instância de serviço além da conclusão da transação. Exemplos disso seriam quando o estado necessário ou os manipuladores para recursos são caros para recuperar ou reconstituir. Você pode fazer isso definindo a ReleaseServiceInstanceOnTransactionComplete propriedade comofalse
. Com essa configuração, a instância e qualquer estado associado estarão disponíveis em chamadas subsequentes. Ao usar isso, considere cuidadosamente quando e como o estado e as transações serão compensados e concluídos. O exemplo a seguir demonstra como fazer isso mantendo a instância com arunningTotal
variável.[ServiceBehavior(TransactionIsolationLevel = [ServiceBehavior( ReleaseServiceInstanceOnTransactionComplete = false)] public class CalculatorService : ICalculator { double runningTotal = 0; [OperationBehavior(TransactionScopeRequired = true)] public double Add(double n) { // Perform transactional operation RecordToLog($"Adding {n} to {runningTotal}"); runningTotal = runningTotal + n; return runningTotal; } [OperationBehavior(TransactionScopeRequired = true)] public double Subtract(double n) { // Perform transactional operation RecordToLog($"Subtracting {n} from {runningTotal}"); runningTotal = runningTotal - n; return runningTotal; } private static void RecordToLog(string recordText) { // Database operations omitted for brevity } }
Nota
Como o tempo de vida da instância é um comportamento interno ao serviço e controlado por meio da ServiceBehaviorAttribute propriedade, nenhuma modificação na configuração do serviço ou no contrato de serviço é necessária para definir o comportamento da instância. Além disso, o fio não conterá nenhuma representação disso.