管理跨多个方法和网络呼叫的事务

时间:2017-06-07 10:10:27

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

我有一个从它调用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下面是一个称为多个地方的通用方法。

if (response.IsSuccessStatusCode)
   {    
     //Commit transactio        
      objTransaction.EndTransaction(true);                                            
   }

现在,如果在调用Web API之前发生异常,那么将回滚所有事务。没事。如果在Web Api中发生异常,则相同。

如果Web API成功执行后会发生异常,在main方法中提交事务时,如何处理它,我的意思是在下面的代码中

(?<preandconjunct>(?:\b([Ss]ubsection|[Ss]ection|[Aa]rticle) +)(?<conjunct>(?:(?<level>(?:(?:[IVXivx]{1,5}(?![A-Z]))|(?:[A-Z]{1,2}(?![A-Z]))|(?:[0-9]+)))|(?<level>\((?:(?:[IVXivx]{1,5}(?![A-Z]))|(?:[A-Z]{1,2}(?![A-Z]))|(?:(?!in|or|if|of|to|as|at|it|no|an)[a-z]{1,2}(?![a-z]))|(?:[0-9]+))\))|(?<level>[\.-](?:(?:[IVXivx]{1,5}(?![A-Z]))|(?:[A-Z]{1,2}(?![A-Z]))|(?:[0-9]+))))+)(?=$|[ ,;.)]))

有没有更好的方法来处理逻辑?

1 个答案:

答案 0 :(得分:1)

您当前的实现不保证原子性,考虑UpdateTax方法完成后出现错误(网络问题或其他)的情况,因此即使您的数据已保存,您的main方法也会收到错误。< / p>

您可以使用TransactionScope创建分布式事务来改进它。

为了使分布式事务起作用,您需要打开分布式事务处理协调器(DTC):https://msdn.microsoft.com/en-us/library/dd327979.aspx。我还假设您的web api应用程序位于主应用程序的同一域网络中。

在您的情况下,您可以创建环境事务以在应用程序边界内的方法调用之间传递事务,如@kayess在其注释中所建议的那样。但是环境事务在网络调用中不起作用,你必须为此实现额外的逻辑。

我们的想法是将您的交易ID 流程发送到您的网络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          
           }  
         }
    }

您的网络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以获得更好的代码可重用性,......)