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?
5 Answer(s)
-
0
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 supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
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 supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
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 supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
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 supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
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 usingTask.Run. This ensures that it gets a cleanExecutionContextand is not affected by the failing ambientUnitOfWork.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.Runcreates a new execution context, which bypasses the corruptedUnitOfWorkfrom the failed try block.Markdown is supportedCopy & paste or drag & drop images (max 30 MB per image)