我有一个从中调用 Web Api 的方法。数据被插入到多个表中,因此添加了事务。下面是代码布局。
{
TransactionObject objTransaction = new TransactionObject();
try
{
//Create Order
order.insert(objTransaction);
//Create Delivery
address.insert(objTransaction);
//Call Generic Web API method to calculate TAX.
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:85766/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.PostAsJsonAsync("api/Tax/UpdateTax", order).Result;
if (response.IsSuccessStatusCode)
{
//Commit transactio
objTransaction.EndTransaction(true);
}
else
{
//Commit transactio
objTransaction.EndTransaction(false);
}
}
}
}
catch(Exception ex)
{
//Commit transactio
objTransaction.EndTransaction(false);
}
此方法调用 Web api 来执行另一个数据库操作。下面的 Web Api 是一个通用方法,在多个地方调用。
[HttpPost]
public HttpResponseMessage UpdateTax(Order order)
{
TransactionObject objTransaction = new TransactionObject();
try
{
//DO calculation logic
.
.
//Insert Invoice
invoice.insert(objTransaction);
objTransaction.EndTransaction(true);
return Request.CreateResponse(HttpStatusCode.Created, "Invoice data added successfully");
}
catch (Exception ex)
{
objTransaction.EndTransaction(false);
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Erorr while adding invoice");
}
}
现在,如果在调用 Web API 之前发生异常,则所有事务都将回滚。没事。 Web Api 中发生异常时也是如此。
如果Web API执行成功后,在main方法中提交事务时出现异常,如何处理,我的意思是在下面的代码中
if (response.IsSuccessStatusCode)
{
//Commit transactio
objTransaction.EndTransaction(true);
}
有没有更好的方法来处理逻辑?
最佳答案
您当前的实现不能保证原子性,请考虑在 UpdateTax
方法完成后出现错误(网络问题或其他问题)的情况,因此您的 main 方法会收到错误,即使您的数据已已保存。
您可以使用 TransactionScope 来改进它创建分布式事务。
为了使分布式事务工作,您需要打开分布式事务协调器(DTC):https://msdn.microsoft.com/en-us/library/dd327979.aspx 。我还假设您的 Web API 应用程序与主应用程序位于同一域网络中。
在您的情况下,您可以创建一个环境事务,以使事务在应用程序边界内的方法调用之间流动,正如 @kayess 在其评论中所建议的那样。但是环境事务不能跨网络调用工作,您必须为此实现额外的逻辑。
这个想法是将您的交易 ID流到您的 Web API 应用程序,以便它可以参与您的主应用程序的同一交易。下面是您的主应用程序的示例代码:
using (var scope = new TransactionScope()) //create ambient transaction by default.
{
//Create Order (insert Order using your local method)
//Create Delivery (insert Delivery using your local method)
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:85766/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Send current transaction id as header in the http request
var token = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
//You could improve it by creating a HttpRequestMessage, here is just to demonstrate the idea.
client.DefaultRequestHeaders.Add("TransactionToken", Convert.ToBase64String(token));
var response = client.PostAsJsonAsync("api/Tax/UpdateTax", order).Result;
//Do some more stuff
..
//If there is no exception and no error from the response
if (response.IsSuccessStatusCode) {
scope.Complete(); //Calling this let the transaction manager commit the transaction, without this call, the transaction is rolled back
}
}
}
您的 Web API 方法:
[HttpPost]
public HttpResponseMessage UpdateTax(Order order)
{
try
{
//Retrieve the transaction id in the header
var values = Request.Headers.GetValues("TransactionToken");
if (values != null && values.Any())
{
byte[] transactionToken = Convert.FromBase64String(values.FirstOrDefault());
var transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(transactionToken);
//Enlist in the same transaction
using(var transactionScope = new TransactionScope(transaction)
{
//DO calculation logic
.
.
//Insert Invoice
transactionScope.Complete();
}
}
return Request.CreateResponse(HttpStatusCode.Created, "Invoice data added successfully");
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Erorr while adding invoice");
}
}
注意:
请帮助调整您的代码以适应此情况。我只是演示这个想法(没有检查 null,没有调整旧的数据库方法来与 TransactionScope 一起使用,提取到 ActionMethod 以获得更好的代码可重用性,...)
2020 年更新: 分布式事务是一个非常强的一致性协议(protocol),但它也存在问题:
- 它是一种同步(阻塞协议(protocol)),可以在许多系统上放置大量锁。
- 分布式事务在 RPC 调用方面存在较长的延迟,尤其是在与外部服务集成时。 因此锁可能会被长时间持有并成为性能瓶颈。
如果可能,我们应该尝试使用消息队列(如 RabbitMq)的集成模式,或使用分布式流平台(如 Kafka)构建系统
关于asp.net - 管理跨多种方法和网络调用的事务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44409690/