Compartilhar via


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

  1. 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);  
    }  
    
  2. 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étodo Add é a mesma que a transação de entrada obrigatória que fluiu do cliente, e a transação usada para o método Subtract é 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.  
        }  
    }  
    
  3. 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 atributo bindingConfiguration que referencia uma configuração de associação chamada transactionalOleTransactionsTcpBinding, 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 atributo transactionProtocol, 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

  1. 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 do wsHttpBinding 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

  1. 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ção Debit a seguir. A transação usada pela operação Debit não é concluída até que um método com a propriedade TransactionAutoComplete definida como true seja chamado, conforme mostrado na operação Credit1, ou quando o método SetTransactionComplete é chamado para marcar explicitamente a transação como concluída, conforme mostrado na operação Credit2. 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;  
        }  
    }  
    
  2. 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 propriedade SessionMode 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

  1. 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 como false. 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ável runningTotal.

    [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.