Base solution for your next web application
Open Closed

New unitofwork doesnt seem to work #12469


User avatar
0
uenlkr4e created

Hi, i have such setup and for some reason i cant get the new unitofwork in catch block work.

``try{
using var uow = unitOfWorkManager.Begin();
//do something
await uow.CompleteAsync();
}
catch{
using (unitOfWorkManager.Begin())
            {
 //do other things
 await unitOfWorkManager.Current.SaveChangesAsync();    
}
}

if something goes wrongin try block, everything is rolled back including the db operations in catch block (do other things). am i doing something wrong?

Markdown is supported
Copy & paste or drag & drop images (max 30 MB per image)

5 Answer(s)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @uenlkr4e

    ABP manages UnitOfWork using an ambient context (via AsyncLocal). So when an exception happens inside a UnitOfWork, the context remains tainted. If you try to start a new UnitOfWork in the same async context (like inside catch), it might still behave as if it's part of the failed transaction.

    Start a new UnitOfWork with the parameter Scope = TransactionScopeOption.RequiresNew, so there is no connection to the previous UnitOfWork.

    try
    {
        using var uow = unitOfWorkManager.Begin();
        // do something
        await uow.CompleteAsync();
    }
    catch
    {
        using (var uow = unitOfWorkManager.Begin(
            new UnitOfWorkOptions
            {
                IsTransactional = true,
                Scope = System.Transactions.TransactionScopeOption.RequiresNew
            }))
        {
            // do other things
            await unitOfWorkManager.Current.SaveChangesAsync();
            await uow.CompleteAsync();
        }
    }
    

    If the issue persists after applying this solution, please don’t hesitate to contact us again. Thank you

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    uenlkr4e created

    Hi, unfortunately this doesnt seem to work. even though the catch block code run, the entity that i am trying to update in catch block is not updated.

    catch (Exception e)
            {
                using (var unitOfWork = unitOfWorkManager.Begin(
                           new UnitOfWorkOptions
                           {
                               IsTransactional = true,
                               Scope = System.Transactions.TransactionScopeOption.RequiresNew
                           }))
                {
                    var invoice = await invoiceManager.GetAsync(invoiceId);
                    invoice.Status = StatusEnum.Error;
                    invoice.StatusDescription = e.Message;
                    await invoiceRepository.UpdateAsync(invoice);
                    //await unitOfWorkManager.Current.SaveChangesAsync();
                    await unitOfWork.CompleteAsync();
                }
    
                throw new UserFriendlyException(
                    $"Something went wrong {e.Message}. Inv. Id: {invoice.Id}. "
                );
            }
    

    i commented savechangesasync since completeasync does the same but tried both commented, uncommented. nothing seems to work. the invoice status and desc stays the same.

    any idea? thanks

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @uenlkr4e

    Force refresh the repository In the first approach, we directly use the repository within a new UnitOfWork with RequiresNew scope to isolate the update. This ensures it is not affected by the outer transaction rollback.

    catch (Exception e)
    {
        using (var unitOfWork = unitOfWorkManager.Begin(
                   new UnitOfWorkOptions
                   {
                       IsTransactional = true,
                       Scope = System.Transactions.TransactionScopeOption.RequiresNew
                   }))
        {
            var invoice = await invoiceRepository.FirstOrDefaultAsync(x => x.Id == invoiceId); //Invoice Repository
            if (invoice != null)
            {
                invoice.Status = StatusEnum.Error;
                invoice.StatusDescription = e.Message;
                
                await invoiceRepository.UpdateAsync(invoice);
            }
            
            await unitOfWork.CompleteAsync();
        }
        
        throw new UserFriendlyException($"Something went wrong {e.Message}. Inv. Id: {invoiceId}.");
    }
    

    Completely isolate with the Separate method In the second approach, we further isolate the logic by moving it into a separate method decorated with [UnitOfWork(IsDisabled = false)].

    catch (Exception e)
    {
        await UpdateInvoiceStatusInSeparateTransaction(invoiceId, e.Message);
        throw new UserFriendlyException($"Something went wrong {e.Message}. Inv. Id: {invoiceId}.");
    }
    
    
    [UnitOfWork(IsDisabled = false)]
    public virtual async Task UpdateInvoiceStatusInSeparateTransaction(int invoiceId, string errorMessage)
    {
        using (var uow = unitOfWorkManager.Begin(new UnitOfWorkOptions
        {
            IsTransactional = true,
            Scope = TransactionScopeOption.RequiresNew
        }))
        {
            var invoice = await invoiceManager.GetAsync(invoiceId);
            if (invoice != null)
            {
                invoice.Status = StatusEnum.Error;
                invoice.StatusDescription = errorMessage;
                
                await invoiceRepository.UpdateAsync(invoice);
            }
            await uow.CompleteAsync();
        }
    }
    

    Both solutions should work provided the update is completely separated from the external failing process. Can you please let us know if the issue persists after applying these changes? Thanks.

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    uenlkr4e created

    Unfortunately this didnt work either. I am getting the following exception "An exception has been raised that is likely due to a transient failure."

    Any idea why i might be getting this when the uow.CompleteAsync() is called?

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @uenlkr4e

    Can you try this solution in your scenario? To completely isolate the new UnitOfWork, we can run it in a new asynchronous context using Task.Run. This ensures that it gets a clean ExecutionContext and is not affected by the failing ambient UnitOfWork.

    catch (Exception e)
    {
        await Task.Run(async () =>
        {
            using (var uow = unitOfWorkManager.Begin(
                       new UnitOfWorkOptions
                       {
                           IsTransactional = true,
                           Scope = System.Transactions.TransactionScopeOption.RequiresNew
                       }))
            {
                var invoice = await invoiceRepository.FirstOrDefaultAsync(x => x.Id == invoiceId);
                if (invoice != null)
                {
                    invoice.Status = StatusEnum.Error;
                    invoice.StatusDescription = e.Message;
    
                    await invoiceRepository.UpdateAsync(invoice);
                }
    
                await uow.CompleteAsync();
            }
        });
    
        throw new UserFriendlyException($"Something went wrong: {e.Message}. Inv. Id: {invoiceId}.");
    }
    

    Task.Run creates a new execution context, which bypasses the corrupted UnitOfWork from the failed try block.

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)