为什么WCF分布式事务不回滚?

时间:2011-05-02 16:58:28

标签: wcf distributed-transactions 2phase-commit

如Juval Lowy的“编程WCF服务”中所述,WCF使用两阶段提交协议(2PC)来管理分布式事务:

  

当交易结束时,交易管理员检查参与服务的组合投票。如果任何服务或客户投票中止,则交易注定失败:指示所有参与资源放弃交易期间所做的更改。

我针对事务测试了不同的资源管理器(VRM,sql server,...),但它们都没有正常工作。主要问题是中止分布式事务不会回滚在资源上应用的更改。

My source由两个控制台程序(客户端和服务)组成。每次调用AddValue服务操作都会向sql-server数据库表添加一条记录(一个整数字段)。对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生命周期依赖于服务生命周期的情况。在分布式tranasction结束之前,LTM或其他东西是否保存对资源的引用?如果当时资源不可用怎么办?

0 个答案:

没有答案