我目前正在尝试针对SQL Server 2016编写一个简单的C#应用程序来使用Microsoft分布式事务(MSDTC)。在尝试通过网络在服务器上测试之前,我试图让它在我的本地计算机(Windows 10 Pro)上运行。
.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();
}
初始化throw new Exception();
后,第三个webapi项目已TransactionScope
。
要在我的机器上设置MSDTC,我已按照here所述的文章执行了以下操作:
检查“分布式事务协调器”服务是否在“管理工具”中运行 - >服务。
设置了Windows防火墙以允许分布式事务:
控制面板 - > Windows防火墙 - >通过Windows防火墙允许应用程序或功能 - >在列表中添加了“分发事务处理协调器”。
在组件服务中启用网络事务:
组件服务 - > Exapand Computers - >展开MyComputer - >展开分布式事务处理协调器 - >右键单击“本地DTC”并选择属性 - >选择“安全”选项卡
这些是当前设置:
我一直在查看here所描述的一些故障排除信息。本文中的其中一个步骤介绍了如何下载DTCTool并运行RMClient.exe。我已经完成了这个并生成了以下日志:
错误(RpcStatus = 1753):在RPCuser.cpp(行:116)RPC服务器过程 LogOn失败(1753)
DEBUG TIP ::此错误表示客户端能够查询 指定目标计算机上端口135的端点映射器,但也可以 无法联系WinRM服务器。检查:
- 防火墙阻止更高端口 - 在客户端和服务器之间获取netmon跟踪并分析此行为的流量
- 如果在服务器上启用了Windows防火墙,请检查WinRM.exe是否在例外列表中
- 在群集环境中检查群集名称是否正在解析运行MSDTC / WinRM的节点
- 在群集环境中,在客户端和MSDTC群集名称之间执行DTCPing测试,以查看客户端是否无法登录服务器
醇>
我不确定这个日志是否相关,但我现在有点卡住了。我暂时关闭了防火墙,它仍然没有工作,所以第1点和第2点应该不是问题。而且我没有使用集群环境,因此第3点和第4点不应成为问题。
我不确定我是否要在我的应用程序中正确设置MSDTC。我刚刚从互联网上找到了我能找到的关于它的点点滴滴。对此有任何帮助将非常感激。
谢谢
答案 0 :(得分:0)
用户Pankaj Kapare提供的link对解决此问题非常有帮助。基本上,我实际上并没有将在控制台应用程序(根事务管理器)中初始化的事务传递给任何web api应用程序(这应该是显而易见的)。因此,每个web api应用程序都在初始化自己的事务而不是参与现有事务。
要总结链接,可以使用TransactionInterop将控制台应用程序中初始化的主事务检索为令牌,并作为请求标头或cookie的一部分传递给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;
}
}
}
}