TransactionScope does not Rollback when even when an exception is thrown

73 views Asked by At

I making a few API calls inside the transaction block. From what I know if any of these APIs throw an exception then the transaction should rollback. By rollback I understand that all the tasks that the APIs that successfully executed did is undone. The problem is that even if an API throws an exception the scope.Complete() is reached and the rollback does not take place.

This is my transaction block. The method it is being called in does not have a try catch block

using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    await UpdateAuditPlanL3Async(auditCalender);
    await SaveAuditManagementL3(auditMgmt);
    await SaveAuditManagementStdDetaiL3(mgmtStdDetails);
    await SaveAuditObservationDetailL3(obsdetail);
    scope.Complete();
}

The parameters to the awaited methods are lists

This below is a sample code to show the way i am calling the API

public async Task UpdateAuditPlanL3Async(AuditCalendarL3 auditCalendarl3)
{
    string url = string.Concat(GlobalDeclaration.APIServerUrl, "UpdateAuditPlanL3?AuditplanId=" + auditCalendarl3.AuditplanId + "&Token=" + GlobalDeclaration.ApiToken);
    url = GlobalDeclaration.CleanURL(url);
    var myContent = JsonConvert.SerializeObject(auditCalendarl3);
    var requestContent = new StringContent(myContent, Encoding.UTF8, "application/json");
    var response = await Program.httpClient.PutAsync(url, requestContent);
    var responseCode = response.StatusCode;
    if (responseCode == HttpStatusCode.OK || responseCode == HttpStatusCode.Created || responseCode == HttpStatusCode.Accepted)
    {
        GlobalDeclaration.log.Info("UpdateAuditPlanL3 API executed successfully. The response code is " + responseCode);
    }
    else
    {
        GlobalDeclaration.exlog.Error("UpdateAuditPlanL3 API failed. The response code is " + responseCode);
        throw new Exception("UpdateAuditPlanL3 API execution failed.");
    }
}

In my case the below API is throwing an exception. Now i know why the exception is being thrown. I want to know how can i solve the rollback not happening even when the exception is being thrown

private async Task SaveAuditObservationDetailL3(List<AuditObservationDetailL3> adobsdtl)
{
    string url = string.Concat(GlobalDeclaration.APIServerUrl, "SaveAuditObservationDetailL3?Token=" + GlobalDeclaration.ApiToken);
    url = GlobalDeclaration.CleanURL(url);
    var myContent = JsonConvert.SerializeObject(adobsdtl);
    var requestContent = new StringContent(myContent, Encoding.UTF8, "application/json");
    var response = await Program.httpClient.PostAsync(url, requestContent);
    var responseCode = response.StatusCode;
    if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created || response.StatusCode == HttpStatusCode.Accepted)
    {
        GlobalDeclaration.log.Info("SaveAuditObservationDetailL3 API executed successfully. The response code is " + responseCode);
        MessageBox.Show("Observation Checkpoints created successfully.");
    }
    else
    {
        GlobalDeclaration.exlog.Error("SaveAuditObservationDetailL3 API failed. The response code is " + responseCode);
        throw new Exception("SaveAuditObservationDetailL3 API failed. The response code is " + responseCode);
    }
}

I have read on how to use transactionscope and everywhere it says that the rollback should occur. I also tried using a try catch block inside the transaction scope but to no avail. I even removed the TransactionScopeAsynFlowOption.Enabled and also tried the .Suppress but it always reaches the scope.Complete() even when an exception is thrown.

1

There are 1 answers

8
Marc Gravell On

The problem is that even if an API throws an exception the scope.Complete() is reached...

No, I do not think that is what is happening here. A few more likely options:

  1. no exception is being thrown
  2. the exception is being swallowed (caught and not rethrown) by the individual method
  3. an exception is thrown and the scope is rolled back, but the thing you want to rollback doesn't support ambient transactions (very few things do)
  4. an exception is thrown and the scope is rolled back, and the thing you want to rollback does support ambient transactions, but the usage is not correct - for example, a SqlConnection must be opened inside the ambient transaction in order for it to support this usage

Given that you've shown some of the HTTP API code, I think we can rule out 2, and you seem confident that we can rule out 1, and you have enabled async flow in thr transaction (that was going to be my item zero), so that leaves 3 and 4; what is the thing that you are expecting to rollback? Can we see that, and how confident are you that it supports ambient transactions?

To be clear: random HTTP calls do not support transactions, ambient or explicit. Transactions require lots of coordination, requiring lots of logic and work on both sides; WCF offers some of this, for example; it may also require additional services such as DTC. Mostly, ambient transactions tend to be restricted to SQL work (on a subset of ADO.NET providers), because SQL databases at least usually have a concept of transactions; raw HTTP does not.

(side note: ambient transactions are convenient, but almost always a bad approach, although this is somewhat subjective, or at least contextual)