Почему распределенная транзакция WCF не выполняет откат? - PullRequest
0 голосов
/ 02 мая 2011

Как описано в «Программировании служб WCF» Джувала Лоуи, WCF использует протокол двухфазной фиксации (2PC) для управления распределенными транзакциями:

Когда транзакция заканчивается, менеджер транзакций проверяет объединенное голосование.участвующих услуг.Если какой-либо сервис или клиент проголосовал за прерывание, транзакция обречена: всем участвующим ресурсам дано указание отменить изменения, внесенные во время транзакции.

Я тестировал разные менеджеры ресурсов (VRM, sql server,...) против транзакций, но ни одна из них не работала правильно.Основная проблема заключается в том, что прерывание распределенной транзакции не отменяет изменения, примененные к ресурсу.

Мой источник состоит из двух консольных программ (клиент и служба).Каждый вызов операции сервиса AddValue добавляет запись (целочисленное поле) в таблицу базы данных sql-сервера.Второй вызов AddValue вызывает исключение, которое прерывает распределенную транзакцию.

Запустите службу, а затем программу клиента.Теперь просмотрите массив values3 (в клиентской программе), это будет {1, 2}, что означает, что изменения не отменены.

Сервисный контракт:

[ServiceContract(SessionMode=SessionMode.Required)]
public interface IExercise
{
    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Mandatory)]
    void AddValue(int value);

    [OperationContract]
    int[] GetValues();
}

Реализация сервиса:

[ServiceBehavior(
    InstanceContextMode = InstanceContextMode.PerSession,
    ReleaseServiceInstanceOnTransactionComplete = false,
    ConcurrencyMode = ConcurrencyMode.Single,
    TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable,
    TransactionTimeout = "00:5:0")]
[BindingRequirement(TransactionFlowEnabled = true)]
[ErrorHandlerBehavior]
public class ExerciseService : IExercise
{
    #region IExercise

    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
    public void AddValue(int value)
    {
        Debug.Assert(Transaction.Current != null &&
            Transaction.Current.TransactionInformation.DistributedIdentifier != Guid.Empty);

        AddDbValue(value);
        //AddVRMValue(value); 

        if (value == 2)
            throw new Exception("abort tx");

        //OperationContext.Current.SetTransactionComplete();
        //Transaction.Current.Rollback();
    }

    public int[] GetValues()
    {
        var result = GetDbValues();
        //var result = GetVRMValues();
        return result;
    }

    #endregion

    #region db

    private const string Table1 = "dbo.Table1";
    private const string Column1 = "Column1";

    private static SqlConnection connection;

    static ExerciseService()
    {
        var text = Properties.Settings.Default.ExerciseConnectionString;
        connection = new SqlConnection(text);
        connection.Open();
    }

    private void AddDbValue(int value)
    {
        var text = "insert into " + Table1 + " values (" + value + ")";
        using (var command = new SqlCommand(text, connection))
        {
            command.ExecuteNonQuery();
        }
    }

    private int[] GetDbValues()
    {
        using (var command = new SqlCommand("select * from " + Table1, connection))
        using (var reader = command.ExecuteReader())
        {
            var result = reader.ReadColumn<int>(Column1).ToArray();
            return result;
        }
    }

    #endregion

    #region VRM

    private static TransactionalList<int> Storage = new TransactionalList<int>();

    private void AddVRMValue(int value)
    {
        Storage.Add(value);
    }

    private int[] GetVRMValues()
    {
        var result = Storage.ToArray();
        return result;
    }

    #endregion
}

Сервисный прокси:

public class ExerciseClient : ClientBase<IExercise>, IExercise
{
    #region IExercise

    public void AddValue(int value)
    {
        Channel.AddValue(value);
    }

    public int[] GetValues()
    {
        var result = Channel.GetValues();
        return result;
    }

    #endregion
}

Клиентская программа:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (var proxy2 = new ExerciseClient())
            using (var proxy = new ExerciseClient())
            {
                using (var s = new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromMinutes(5)))
                {
                    proxy.AddValue(1);
                    var values = proxy.GetValues();

                    proxy2.AddValue(2);
                    var values2 = proxy2.GetValues();

                    s.Complete();
                }
            }
        }
        catch
        {
        }

        using (var proxy3 = new ExerciseClient())
        {
            var values3 = proxy3.GetValues();
        }
    }
}

А это файл конфигурации службы:

  <services>

    <service name="Tofigh.Exercise.ExerciseService">

      <endpoint address="net.tcp://localhost/Exercise"
                binding="netTcpBinding"
                bindingConfiguration="bindingConfiguration"
                contract="Tofigh.Exercise.IExercise"
                />

    </service>

  </services>

  <bindings>
    <netTcpBinding>

      <binding name="bindingConfiguration"
               transactionFlow="true">
        <reliableSession enabled="true" />
      </binding>

    </netTcpBinding>
  </bindings>

Мой второй вопрос: как DTC вызывает RM для отката изменений в случае прерывания транзакции?Рассмотрим случаи, когда жизненный цикл RM зависит от жизненного цикла обслуживания.Сохраняет ли LTM или что-то еще ссылку на ресурс, пока не закончится распределенная трансация?Что делать, если ресурс в это время недоступен?

...