为什么我的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和执行了以下内容:
经过了“分布式事务处理协调器”服务管理工具运行 - >服务。
-
先后成立了Windows防火墙以允许分布式事务:
控制面板 - > Windows防火墙 - >允许程序通过Windows防火墙的应用程序或功能 - >添加“分布事务处理协调器”到列表中。
-
启用网络交易中的组件服务:
组件服务 - > Exapand电脑 - >展开我的电脑 - >展开分布式事务协调器 - >右键单击“本地DTC”,然后选择属性 - >选择安全选项卡
这些都是当前设置:
我也一直在寻找here中描述的一些故障排除信息。本文中的其中一个步骤描述了下载DTCTool并运行RMClient.exe。我已经做到了这一点,它已经产生了以下日志:
错误(RpcStatus = 1753):在RPCuser.cpp(线:116)RPC服务器程序 登录失败(1753)
DEBUG提示: :此错误表示客户端能够在指定的目标计算机上的端口135上查询 端点映射器,但是 无法联系WinRM服务器。检查:
- 防火墙阻塞更高端口 - 获取客户端和服务器之间的网络监视器跟踪和分析流量这种行为
- 如果Windows防火墙服务器上启用了检查,如果WinRM.exe是例外名单
- 在群集环境检查,如果群集名称解析为其中MSDTC/WinRM的运行
- 在集群环境中做的客户端和MSDTC群集名称之间的DTCPing测试,看看是否能工作的客户端无法登录节点服务器
我不确定这个日志是否相关,但我现在有点卡住了。我暂时关闭了防火墙,但仍然无法工作,所以第1点和第2点应该不成问题。而且我没有使用集群环境,所以点3和4应该不成问题。
我不确定是否要在我的应用程序中正确设置MSDTC。我只是把我能从互联网上找到的关于它的点点滴滴放在一起。任何帮助,将非常感激。
谢谢
用户的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;
}
}
}
}
您错过了您的服务调用实际上传输交易信息的任何信息。您是否在服务上设置了任何属性来自动执行此操作?只是使用'TransactionScope'不能保证分布式事务,你必须*分发*它。 – nvoigt
我相信每次在使用语句中创建TransactionScope时,它都会创建新的作用域。所以调用3个apis的事务范围与个人api中创建的不同。您需要在同一个事务范围内运行所有三个Webapis,然后在任何一个失败的情况下回滚。你可以看看这个示例https://code.msdn.microsoft.com/Distributed-Transactions-c7e0a8c2 –
@nvoigt - 感谢您的信息。我错误地认为当前的事务会被底层的MSDTC传递,但现在我正在考虑这个事情,这没有任何意义,特别是当您考虑多用户环境中的多个事务时。 – Dangerous