Транзакционное поведение службы

В примере "Транзакции" демонстрируется использование клиентской транзакции и параметров ServiceBehaviorAttribute и OperationBehaviorAttribute для управления поведением транзакций службы. Этот пример основан на примере Getting Started, который реализует службу калькулятора, но дополнительно поддерживает ведение журнала выполненных операций в таблице базы данных и поддерживает общее состояние для операций калькулятора. Устойчивые операции записи в таблицу журнала сервера зависят от результата координируемой клиентом транзакции - если транзакция клиента не завершается, транзакция веб-службы гарантирует, что обновления базы данных не будут зафиксированы.

Примечание.

Процедура настройки и инструкции по построению для данного примера приведены в конце этого раздела.

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

[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);
}

Чтобы включить поток входящих транзакций, для службы настраивается системная привязка wsHttpBinding, атрибут transactionFlow которой имеет значение true. Эта привязка использует поддерживающий взаимодействие протокол WSAtomicTransactionOctober2004:

<bindings>
  <wsHttpBinding>
    <binding name="transactionalBinding" transactionFlow="true" />
  </wsHttpBinding>
</bindings>

После инициации подключения к службе и транзакции клиент обращается к нескольким операциям службы в пределах области транзакции, а затем завершает транзакцию и закрывает подключение:

// 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();

На стороне службы имеется три атрибута, которые влияют на поведение транзакции службы. Это происходит следующим образом.

  • В атрибуте ServiceBehaviorAttribute.

    • Свойство TransactionTimeout задает период времени, в течение которого транзакция должна быть завершена. В этом образце это время составляет 30 секунд.

    • Свойство TransactionIsolationLevel указывает уровень изоляции, поддерживаемый службой. Он должен совпадать с уровнем изоляции клиента.

    • Свойство ReleaseServiceInstanceOnTransactionComplete определяет, используется ли экземпляр службы повторно по завершении транзакции. При установке для него значения false служба использует один и тот же экземпляр службы для разных запросов операций. Это требуется для поддержания нарастающего итога. Если свойство имеет значение true, после каждого завершенного действия создается новый экземпляр.

    • Свойство TransactionAutoCompleteOnSessionClose определяет, завершаются ли незавершённые транзакции при завершении сеанса. При установке значения false, индивидуальные операции должны либо установить свойство OperationBehaviorAttribute.TransactionAutoComplete на true, либо явно вызвать метод OperationContext.SetTransactionComplete() для завершения транзакций. В этом образце продемонстрированы оба подхода.

  • В атрибуте ServiceContractAttribute.

    • Свойство SessionMode определяет, согласовывает ли служба соответствующие запросы в логический сеанс. Поскольку служба включает операции, для которых свойство OperationBehaviorAttribute TransactionAutoComplete имеет значение false (Multiply и Divide), необходимо задавать SessionMode.Required. Например, операция Multiply не завершает свою транзакцию и вместо этого ожидает очередного вызова операции Divide, чтобы завершить транзакцию с помощью метода SetTransactionComplete; служба должна уметь определять, что операции выполняются в рамках одного сеанса.
  • В атрибуте OperationBehaviorAttribute.

    • Свойство TransactionScopeRequired определяет, должны ли действия операции выполняться в области транзакции. Оно устанавливается равным true для всех операций в этом образце, и, поскольку клиент направляет свои транзакции всем операциям, действия выполняются в области соответствующей транзакции клиента.

    • Свойство TransactionAutoComplete указывает, следует ли автоматически завершать транзакцию, в которой выполняется метод, при отсутствии необработанных исключений. Как говорилось выше, это свойство устанавливается равным true для операций Add и Subtract и false для операций Multiply и Divide. Операции Add и Subtract завершают свои действия автоматически, операция Divide завершает свои действия через явный вызов метода SetTransactionComplete, а операция Multiply не завершает свои действия, а ожидает очередного вызова, например операции Divide, чтобы завершить свои действия.

Реализация атрибутированной службы следующая.

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

При выполнении примера запросы и ответы операций отображаются в окне консоли клиента. Чтобы закрыть клиент, нажмите клавишу ВВОД в окне клиента.

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.

Журнал запросов операций службы отображается в окне консоли службы. Чтобы закрыть клиент, нажмите клавишу ВВОД в окне клиента.

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

Обратите внимание, что помимо ведения нарастающего итога вычислений, служба также сообщает о создании экземпляров (в данном образце один экземпляр) и записывает операционные запросы в базу данных. Поскольку все запросы зависят от транзакции клиента, в случае невозможности завершить эту транзакцию происходит откат всех операций базы данных. Это можно показать несколькими способами.

  • Закомментируйте клиентский вызов метода tx.Complete() и перезапустите - это приведет к выходу клиента из области транзакции без завершения транзакции.

  • Закомментируйте вызов операции Divide службы — это действие предотвращает завершение действия, инициированного операцией Multiply, что в конечном итоге приводит к неудаче завершения транзакции клиента.

  • Создайте необработанное исключение в любом месте области транзакции клиента - в этом случае клиенту также не удастся завершить транзакцию.

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

Примечание.

В процессе построения файл базы данных копируется в папку bin. Чтобы следить за тем, какие строки сохраняются в журнале, необходимо проверять эту копию файла базы данных, а не файл, входящий в проект Visual Studio.

Установка, сборка и выполнение примера

  1. Убедитесь, что установлен SQL Server 2005 Express Edition или SQL Server 2005. В файле службы App.config может быть задано значение connectionString базы данных либо взаимодействие с базой данных может быть отключено (значение appSettings usingSql равно false).

  2. Чтобы создать выпуск решения на языке C# или Visual Basic .NET, следуйте инструкциям в разделе Building the Windows Communication Foundation Samples.

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в разделе "Примеры Windows Communication Foundation".

При запуске примера на компьютерах необходимо настроить координатор распределенных транзакций Майкрософт (MSDTC), чтобы включить поток сетевых транзакций и использовать средство WsatConfig.exe для поддержки сети транзакций Windows Communication Foundation (WCF).

Настройка координатора распределенных транзакций (Майкрософт) на поддержку выполнения образца на нескольких компьютерах

  1. На сервере службы настройте MSDTC для разрешения входящих сетевых транзакций.

    1. В меню Пуск перейдите к Панели управления, затем Администрированию, а затем Службам компонентов.

    2. Щелкните правой кнопкой мыши мой компьютер и выберите пункт "Свойства".

    3. На вкладке MSDTC щелкните "Конфигурация безопасности".

    4. Проверьте доступ к сети DTC и разрешить входящий трафик.

    5. Нажмите кнопку "Да", чтобы перезапустить службу MS DTC и нажмите кнопку "ОК".

    6. Чтобы закрыть диалоговое окно, нажмите кнопку ОК .

  2. На компьютере службы и на клиентском компьютере настройте брандмауэр Windows, чтобы включить координатор распределенных транзакций (Майкрософт) в список исключений.

    1. С панели управления запустите брандмауэр Windows.

    2. На вкладке "Исключения" нажмите кнопку "Добавить программу".

    3. Перейдите в папку C:\WINDOWS\System32.

    4. Выберите Msdtc.exe и нажмите кнопку "Открыть".

    5. Нажмите кнопку "ОК", чтобы закрыть диалоговое окно "Добавить программу" и нажмите кнопку "ОК", чтобы закрыть applet брандмауэра Windows.

  3. На клиентском компьютере настройте MSDTC, чтобы он разрешал исходящие сетевые транзакции.

    1. В меню Пуск перейдите к Панель управления, затем Служебные программы, и затем Службы компонентов.

    2. Щелкните правой кнопкой мыши мой компьютер и выберите пункт "Свойства".

    3. На вкладке MSDTC щелкните "Конфигурация безопасности".

    4. Проверьте сетевой DTC доступ и разрешите исходящий трафик.

    5. Нажмите кнопку "Да", чтобы перезапустить службу MS DTC и нажмите кнопку "ОК".

    6. Чтобы закрыть диалоговое окно, нажмите кнопку ОК .