asp.net - 管理跨多种方法和网络调用的事务

标签 asp.net exception design-patterns asp.net-web-api transactions

我有一个从中调用 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/

相关文章:

asp.net - 如何从我的 URL 中删除 ASP.NET session ID?

java - 如果从 UncaughtExceptionHandler 内部抛出异常会怎样?

php - 为对象交互设计类和函数

php - 延迟加载?避免它更好吗?

c# - 合并多个配置文件

asp.net - 什么是 ct100?如何重命名它?

java - 错误处理系统如何处理错误?

java - Web.xml 异常类型

c# - 工厂方法模式和抽象工厂模式

c# - 如何在没有显式引用的情况下访问打开的 DbContext?