Поделиться через


Как создавать транзакционные службы

В этом примере показаны различные аспекты создания транзакционной службы и использования инициируемых клиентом транзакций для координации операций службы.

Создание транзакционной службы

  1. Создайте контракт службы и аннотируйте операции с выбранным параметром из перечисления TransactionFlowOption, чтобы задать требования входящих транзакций. Обратите внимание, что в реализуемый класс службы также можно включить атрибут TransactionFlowAttribute. Это позволит одной реализации интерфейса, а не всем реализациям, использовать эти параметры.

    [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. Создайте класс реализации и воспользуйтесь атрибутом ServiceBehaviorAttribute, чтобы задать значения свойств TransactionIsolationLevel и TransactionTimeout (необязательно). Обратите внимание, что во многих случаях можно использовать значения по умолчанию для свойств TransactionTimeout (60 секунд) и TransactionIsolationLevel (Unspecified). Для каждой операции можно с помощью атрибута OperationBehaviorAttribute определить, должны ли операции, расположенные внутри метода, выполняться в области действия транзакции в соответствии со значением атрибута TransactionScopeRequired. В этом случае транзакция, используемая для метода Add, будет совпадать с обязательной входящей транзакцией, которая поступает от клиента, а транзакция, используемая для метода Subtract, либо совпадает со входящей транзакцией, если она поступила от клиента, либо является новой созданной явно локальной транзакцией.

    [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(String.Format("Adding {0} to {1}", n1, n2));
            return n1 + n2;
        }
    
        [OperationBehavior(TransactionScopeRequired = true)]
        public double Subtract(double n1, double n2)
        {
            // Perform transactional operation
            RecordToLog(String.Format("Subtracting {0} from {1}", n2, 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. Настройте привязки в файле конфигурации, указав, что контекст транзакций должен передаваться, и необходимые для этого протоколы. Дополнительные сведения см. в разделе Конфигурация транзакции ServiceModel. В частности тип привязки задается в атрибуте binding элемента конечной точки. Элемент <endpoint> содержит атрибут bindingConfiguration, который ссылается на конфигурацию привязки с именем transactionalOleTransactionsTcpBinding, как показано в следующем образце конфигурации.

    <service name="CalculatorService">
      <endpoint address="net.tcp://localhost:8008/CalcService"
        binding="netTcpBinding"
        bindingConfiguration="transactionalOleTransactionsTcpBinding"
        contract="ICalculator"
        name="OleTransactions_endpoint" />
    </service>
    

    Поток транзакций настраивается на уровне конфигурации с помощью атрибута transactionFlow, а протокол транзакций задается с помощью атрибута transactionProtocol, как показано в следующей конфигурации.

    <bindings>
      <netTcpBinding>
        <binding name="transactionalOleTransactionsTcpBinding"
          transactionFlow="true"
          transactionProtocol="OleTransactions"/>
      </netTcpBinding>
    </bindings>
    

Поддержка нескольких протоколов транзакций

  1. Для достижения оптимальной производительности в сценариях, включающих клиенты и серверы, написанные с помощью Windows Communication Foundation (WCF), следует использовать протокол OleTransactions. Однако в сценариях, требующих взаимодействия со сторонним стеком протоколов, удобно использовать протокол WS-AtomicTransaction (WS-AT). Службы WCF можно настроить таким образом, чтобы они поддерживали оба протокола. Для этого необходимо задать несколько конечных точек с соответствующими привязками, как показано в следующем примере конфигурации.

    <service name="CalculatorService">
      <endpoint address="https://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>
    

    Протокол транзакций задается с помощью атрибута transactionProtocol. Однако в предоставляемой системой привязке wsHttpBinding этого атрибута нет, поскольку эта привязка может использовать только протокол WS-AT.

    <bindings>
      <wsHttpBinding>
        <binding name="transactionalWsatHttpBinding"
          transactionFlow="true" />
      </wsHttpBinding>
      <netTcpBinding>
        <binding name="transactionalOleTransactionsTcpBinding"
          transactionFlow="true"
          transactionProtocol="OleTransactions"/>
      </netTcpBinding>
    </bindings>
    

Управление завершением транзакций

  1. По умолчанию операции WCF автоматически завершают транзакции, если не создаются необработанные исключения. Такое поведение можно изменить с помощью свойства TransactionAutoComplete и метода SetTransactionComplete. Если операция должна произойти в той же транзакции, что и другая операция (например, операции дебета и кредита), можно отключить автоматическое завершение, задав для свойства TransactionAutoComplete значение false, как показано в приведенном ниже примере операции Debit. Транзакция, используемая операцией Debit, остается незавершенной, пока не будет вызван метод, у которого свойство TransactionAutoComplete имеет значение true, как показано в операции Credit1, или пока не будет вызван метод SetTransactionComplete, явным образом показывающий, что транзакция завершена, как показано в операции Credit2. Обратите внимание, что две операции кредита показаны в этом примере для иллюстрации, и в реальной ситуации обычно используется одна операция кредита.

    [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. Если для согласования транзакций свойству TransactionAutoComplete присваивается значение false, необходимо использовать привязку с отслеживанием состояния. Это требование задается с помощью свойства SessionMode класса 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);
    }
    

Управление временем существования экземпляра транзакционной службы

  1. Чтобы указать, нужно ли освобождать соответствующий экземпляр службы при завершении транзакции, в WCF используется свойство ReleaseServiceInstanceOnTransactionComplete. Поскольку, если не задано иное значение, это свойство по умолчанию имеет значение true, в WCF реализован эффективный и предсказуемый механизм активации, срабатывающий в нужное время. При вызове службы в последующей транзакции используется новый экземпляр службы, который не содержит признаков состояний предыдущих транзакций. Хотя такой подход зачастую бывает удобным, иногда может возникнуть необходимость сохранения состояния службы после завершения транзакции. Например, такая ситуация возникает, если извлечение или восстановление требуемого состояния или дескриптора требует большого объема ресурсов. В этом случае можно задать для свойства ReleaseServiceInstanceOnTransactionComplete значение false. Если этот параметр задан, экземпляр и связанное состояние будут доступны в последующих вызовах. При использовании этого сценария необходимо внимательно следить за тем, как и когда происходят очистка и завершение состояний и транзакций. В следующем примере показано, как реализовать это, поддерживая экземпляр с помощью переменной 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(String.Format("Adding {0} to {1}", n, runningTotal));
            runningTotal = runningTotal + n;
            return runningTotal;
        }
    
        [OperationBehavior(TransactionScopeRequired = true)]
        public double Subtract(double n)
        {
            // Perform transactional operation
            RecordToLog(String.Format("Subtracting {0} from {1}", n, runningTotal));
            runningTotal = runningTotal - n;
            return runningTotal;
        }
    
        private static void RecordToLog(string recordText)
        {
            // Database operations omitted for brevity
        }
    }
    
    ms730232.note(ru-ru,VS.100).gifПримечание
    Поскольку время существования экземпляра определяется внутри службы и управляется через свойство ServiceBehaviorAttribute, чтобы задать поведение экземпляра, изменять конфигурацию службы или контракт службы не требуется. Кроме того, это не отразится на сети.