为什么我的MSDTC事务没有在我的localhost环境中正确回滚?

问题描述:

我目前正在尝试针对SQL Server 2016编写简单的C#应用​​程序以使用Microsoft分布式事务(MSDTC)。在尝试通过网络在服务器上测试它之前,我试图让它在本地计算机上运行(Windows 10 Pro)。为什么我的MSDTC事务没有在我的localhost环境中正确回滚?

.NET解决方案由一个控制台应用程序项目和三个独立的webapi项目组成。控制台应用程序调用每个webapi项目,其中每个webapi项目然后将简单记录写入数据库。这似乎工作正常。然后,我想要做的是让第三个webapi项目生成一个异常,并将所有先前的记录作为分布式事务的一部分回滚。但是,我的问题是,前两个项目将他们的记录提交到数据库,而不是回滚。

我设置它的方式(我相信它是正确的)是每个项目使用System.Transactions参与使用DTC。

控制台应用程序作为根事务管理,并通过执行类似的东西发起交易:

using (TransactionScope scope = new TransactionScope()) 
{ 
    var orderId = Guid.NewGuid(); 
    ProcessSales(orderId); 
    ProcessBilling(orderId); 
    ProcessShipping(orderId); 

    scope.Complete(); 
} 

每个Process...()上述方法使用HttpClient做出其相应的WebAPI项目的调用。

每个项目的WebAPI应在根事务通过其他交易,然后争取:

using (TransactionScope scope = new TransactionScope()) 
{ 
    string connectionString = @"Server=MyServerNameHere;Database=MyDatabaseNameHere;Trusted_Connection=True;"; 

    using (SqlConnection connection = new SqlConnection(connectionString)) 
    { 
     string sql = "INSERT INTO dbo.Shipping (ShippingId, OrderId, Created) VALUES (@ShippingId, @OrderId, GetDate())"; 

     using (SqlCommand command = new SqlCommand(sql, connection)) 
     { 
      ... 
      command.ExecuteNonQuery(); 
     } 
    } 

    scope.Complete(); 
} 

第三的WebAPI项目只是初始化TransactionScope后有throw new Exception();

要我的机器上安装MSDTC我按照文章中介绍here和执行了以下内容:

  1. 经过了“分布式事务处理协调器”服务管理工具运行 - >服务。

  2. 先后成立了Windows防火墙以允许分布式事务:

    控制面板 - > Windows防火墙 - >允许程序通过Windows防火墙的应用程序或功能 - >添加“分布事务处理协调器”到列表中。

  3. 启用网络交易中的组件服务:

    组件服务 - > Exapand电脑 - >展开我的电脑 - >展开分布式事务协调器 - >右键单击“本地DTC”,然后选择属性 - >选择安全选项卡

    这些都是当前设置:

enter image description here

我也一直在寻找here中描述的一些故障排除信息。本文中的其中一个步骤描述了下载DTCTool并运行RMClient.exe。我已经做到了这一点,它已经产生了以下日志:

错误(RpcStatus = 1753):在RPCuser.cpp(线:116)RPC服务器程序 登录失败(1753)

DEBUG提示: :此错误表示客户端能够在指定的目标计算机上的端口135上查询 端点映射器,但是 无法联系WinRM服务器。检查:

  1. 防火墙阻塞更高端口 - 获取客户端和服务器之间的网络监视器跟踪和分析流量这种行为
  2. 如果Windows防火墙服务器上启用了检查,如果WinRM.exe是例外名单
  3. 在群集环境检查,如果群集名称解析为其中MSDTC/WinRM的运行
  4. 在集群环境中做的客户端和MSDTC群集名称之间的DTCPing测试,看看是否能工作的客户端无法登录节点服务器

我不确定这个日志是否相关,但我现在有点卡住了。我暂时关闭了防火墙,但仍然无法工作,所以第1点和第2点应该不成问题。而且我没有使用集群环境,所以点3和4应该不成问题。

我不确定是否要在我的应用程序中正确设置MSDTC。我只是把我能从互联网上找到的关于它的点点滴滴放在一起。任何帮助,将非常感激。

谢谢

+2

您错过了您的服务调用实际上传输交易信息的任何信息。您是否在服务上设置了任何属性来自动执行此操作?只是使用'TransactionScope'不能保证分布式事务,你必须*分发*它。 – nvoigt

+2

我相信每次在使用语句中创建TransactionScope时,它都会创建新的作用域。所以调用3个apis的事务范围与个人api中创建的不同。您需要在同一个事务范围内运行所有三个Webapis,然后在任何一个失败的情况下回滚。你可以看看这个示例https://code.msdn.microsoft.com/Distributed-Transactions-c7e0a8c2 –

+0

@nvoigt - 感谢您的信息。我错误地认为当前的事务会被底层的MSDTC传递,但现在我正在考虑这个事情,这没有任何意义,特别是当您考虑多用户环境中的多个事务时。 – Dangerous

用户的Pankaj Kapare提供的link是解决这一问题非常有帮助。基本上,我并没有真正将在控制台应用程序(根交易管理器)中初始化的交易传递给任何web api应用程序(这应该是显而易见的)。因此,每个web api应用程序都初始化自己的事务,而不是参与现有的事务。

总结链路,在控制台应用程序初始化主事务可以检索如使用TransactionInterop令牌并传递到的WebAPI作为请求头的一部分或饼干:

if (Transaction.Current != null) 
{ 
    var token = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current); 
    request.Headers.Add("TransactionToken", Convert.ToBase64String(token));     
} 

在web API服务器应用程序可以检索交易标记并用于注册主要交易。文章建议使用动作过滤器,其效果非常好:

public class EnlistToDistributedTransactionActionFilter : ActionFilterAttribute 
{ 
    private const string TransactionId = "TransactionToken"; 

    /// <summary> 
    /// Retrieve a transaction propagation token, create a transaction scope and promote 
    /// the current transaction to a distributed transaction. 
    /// </summary> 
    /// <param name="actionContext">The action context.</param> 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     if (actionContext.Request.Headers.Contains(TransactionId)) 
     { 
      var values = actionContext.Request.Headers.GetValues(TransactionId); 
      if (values != null && values.Any()) 
      { 
       byte[] transactionToken = Convert.FromBase64String(values.FirstOrDefault()); 
       var transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(transactionToken); 

       var transactionScope = new TransactionScope(transaction); 

       actionContext.Request.Properties.Add(TransactionId, transactionScope); 
      } 
     } 
    } 

    /// <summary> 
    /// Rollback or commit transaction. 
    /// </summary> 
    /// <param name="actionExecutedContext">The action executed context.</param> 
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
    { 
     if (actionExecutedContext.Request.Properties.Keys.Contains(TransactionId)) 
     { 
      var transactionScope = actionExecutedContext.Request.Properties[TransactionId] as TransactionScope; 

      if (transactionScope != null) 
      { 
       if (actionExecutedContext.Exception != null) 
       { 
        Transaction.Current.Rollback(); 
       } 
       else 
       { 
        transactionScope.Complete(); 
       } 

       transactionScope.Dispose(); 
       actionExecutedContext.Request.Properties[TransactionId] = null; 
      } 
     } 
    } 
}