Como: criar um serviço transacional
Este exemplo demonstra vários aspectos 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.
Como criar um serviço transacional
Crie um contrato de serviço e anote as operações com a configuração desejada da enumeração TransactionFlowOption para especificar os requisitos de transação de entrada. Observe que você também pode colocar o TransactionFlowAttribute da classe de serviço que está sendo implementada. Isso permite que uma só implementação de uma interface use essas configurações de transação, em vez de cada implementação.
[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 um TransactionIsolationLevel e um TransactionTimeout. Você deve observar que, em muitos casos, o TransactionTimeout padrão de 60 segundos e o TransactionIsolationLevel padrão de
Unspecified
são apropriados. Para cada operação, você pode usar o atributo OperationBehaviorAttribute para especificar se o trabalho executado dentro do método deve ocorrer dentro do escopo de um escopo de transação de acordo com o valor do atributo TransactionScopeRequired. Nesse caso, a transação usada para o métodoAdd
é a mesma que a transação de entrada obrigatória que fluiu do cliente, e a transação usada para o métodoSubtract
é igual à transação de entrada se uma fluiu do cliente ou uma nova transação implicitamente e criada no local.[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 deverá ser fluido e os protocolos a serem usados para fazer isso. Para obter mais informações, confira Configuração de transação do ServiceModel. Especificamente, o tipo de associação é especificado no atributo
binding
do elemento do ponto de extremidade. O elemento <endpoint> contém um atributobindingConfiguration
que referencia uma configuração de associaçã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 com o atributo
transactionFlow
, e o protocolo de transação é especificado com o atributotransactionProtocol
, conforme mostrado na configuração a seguir.<bindings> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Como dar suporte a vários protocolos de transação
Para ter um desempenho ideal, você deve usar o protocolo OleTransactions para cenários que envolvam um cliente e um serviço escritos com o WCF (Windows Communication Foundation). No entanto, o protocolo WS-AT (WS-AtomicTransaction) é útil para cenários em que a interoperabilidade com pilhas de protocolo 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 por meio do atributo
transactionProtocol
. No entanto, esse atributo está ausente dowsHttpBinding
fornecido pelo sistema, pois 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>
Como controlar a conclusão de uma transação
Por padrão, as operações do WCF concluirão automaticamente as transações se nenhuma exceção sem tratamento for gerada. Modifique esse comportamento usando a propriedade TransactionAutoComplete e o método SetTransactionComplete. Quando uma operação precisar ocorrer na mesma transação que outra operação (por exemplo, uma operação de débito e crédito), você pode desabilitar o comportamento de preenchimento automático definindo a propriedade TransactionAutoComplete como
false
, conforme mostrado no exemplo de operaçãoDebit
a seguir. A transação usada pela operaçãoDebit
não é concluída até que um método com a propriedade TransactionAutoComplete definida comotrue
seja chamado, conforme mostrado na operaçãoCredit1
, ou quando o método SetTransactionComplete é chamado para marcar explicitamente a transação como concluída, conforme mostrado na operaçãoCredit2
. Observe que as duas operações de crédito são mostradas para fins ilustrativos e que uma só operação de crédito será 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, a definição da propriedade TransactionAutoComplete como
false
exige o uso de uma associação com sessão. Esse requisito é especificado com a propriedadeSessionMode
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); }
Como controlar o tempo de vida de uma instância de serviço transacional
O WCF usa a propriedade ReleaseServiceInstanceOnTransactionComplete para especificar se a instância de serviço subjacente é liberada quando uma transação é concluída. Como isso usa
true
como padrão, a menos que configurado de outra forma, o WCF exibe um comportamento de ativação "just-in-time" eficiente e previsível. As chamadas a um serviço em uma transação seguinte são garantidas em uma nova instância de serviço sem remanescentes do estado da transação anterior. Embora isso seja geralmente útil, às vezes, o ideal é manter o estado na instância de serviço além da conclusão da transação. Entre os exemplos disso estão as situações em que o estado necessário ou os identificadores dos recursos são caros de serem recuperados ou reconstituídos. Faça isso definindo a propriedade ReleaseServiceInstanceOnTransactionComplete comofalse
. Com essa configuração, a instância e qualquer estado associado estarão disponíveis nas chamadas seguintes. Ao usar isso, considere cuidadosamente quando e como o estado e as transações serão limpos e concluídos. O exemplo a seguir demonstra como fazer isso mantendo a instância com a variávelrunningTotal
.[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 } }
Observação
Como o tempo de vida da instância é um comportamento interno para o serviço e é controlado por meio da propriedade ServiceBehaviorAttribute, nenhuma modificação na configuração de serviço ou no contrato de serviço é necessária para definir o comportamento da instância. Além disso, a transmissão não conterá nenhuma representação disso.