This question already has an answer here:
Integrity of my transaction is lost with "TransactionInDoubtException" exception
(1 answer)
Closed 7 years ago.
Does partial commit happen when TransactionInDoubtException is encountered? If so how to roll back the commits/ properly handle TransactionInDoubtException
var option = new TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadCommitted
using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
{
try
{
Context.SaveEmail(_emailInfoList);
context.SaveSyncState(syncState);
scope.Complete();
return true;
}
catch (TransactionInDoubtException ex)
{
// Looks like commit has already taken place
// How to roll back the changes ??
// sth like scope.Rollback()
}
}
According to MSDN:
This exception is thrown when an action is attempted on a transaction
that is in doubt. A transaction is in doubt when the state of the
transaction cannot be determined. Specifically, the final outcome of
the transaction, whether it commits or aborts, is never known for this
transaction.
This exception is also thrown when an attempt is made to commit the
transaction and the transaction becomes InDoubt.
This is a recoverable error.
EDIT:
For recovery:
You have to catch TransactionInDoubtException& write compensate logic with apt checks.
using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
{
try
{
Context.SaveEmail(_emailInfoList);
context.SaveSyncState(syncState);
scope.Complete();
return true;
}
catch (TransactionInDoubtException ex)
{
//check whether any one record from the batch has been partially committed . If committed then no need to reprocess this batch.
// transaction scope should be disposed first .
scope.Dispose();
if (IsReprocessingNeeded(syncState))
throw;
return true;
}
}
/// <returns></returns>
private bool IsReprocessingNeeded(SyncStateDataModal syncState)
{
while (true)
{
try
{
var id = _emailInfoList[0].ID;
bool isEmailsCommitted = Context.GetJournalEmail().FirstOrDefault(a => a.ID == id) != null;
if (!isEmailsCommitted)
return true;
if (context.EmailSynch(syncState.Id) == null)
{
context.SaveSyncState(syncState);
}
return false;
}
catch (Exception ex)
{
Thread.Sleep(TimeSpan.FromMinutes(AppConfiguration.RetryConnectionIntervalInMin));
}
}
}
SOURCE What is the recovery path for TransactionInDoubtException ?
Related
I am using the transaction scope from System.Transactions.
I have this method where I have two insertions in database. The first Localization is inserted, but then rolled back since it fails on the second insertion.
Now the error is not with the data I send. The data is good. When I remove the transaction scope it works.
I get this error:
System.InvalidOperationException: A root ambient transaction was completed before the nested transaction. The nested transactions should be completed first.
It also enters the second catch and disposes the scope. What could be the problem?
This is my code:
public async Task InsertCategory(InsertCategoryRequest request)
{
using var scope = new TransactionScope();
int localizationId;
try
{
localizationId = await _localizationRepository.InsertLocalization(new Localization
{
English = request.NameEN,
Albanian = request.NameAL,
Macedonian = request.NameMK
});
}
catch (Exception e)
{
scope.Dispose();
Log.Error("Unable to insert localization {#Exception}", e);
throw ExceptionHandler.ThrowException(ErrorCode.Localization_UnableToInsert);
}
try
{
await _categoryRepository.InsertCategory(new Category
{
Name = request.NameEN,
LocalizationId = localizationId
});
}
catch (Exception e)
{
scope.Dispose();
Log.Error("Unable to insert category {#Exception}", e);
throw ExceptionHandler.ThrowException(ErrorCode.Category_UnableToInsert);
}
scope.Complete();
scope.Dispose();
}
I found the answer. I looked for such a long time, but after I posted I found the answer lol.
Just added TransactionScopeAsyncFlowOption.Enabled when constructing the Transaction Scope.
I'm working with Azure ServiceBus and i have a queue with several requests, i read the queue, get the data and do some logic. The problem starts when i have more than 1 request in the queue, the first one works fine, but the second one give me this Exception: Distributed transaction completed. Either enlist this session in a new transaction or the NULL transaction.
I read about Distributed Transaction and it is used when there are more than 1 DataBase and this is not my case, so i dont know why i'm getting this error.
Here is my code.
public async Task Save(ObjectDto objectQueueDto)
{
try
{
Object object = await GetByCode(objectQueueDto.code); // Here i get the error in the second request that come from the ServiceBus queue
//Some validations
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
// Insert and Update several tables.
scope.Complete();
}
}
catch (Exception ex)
{
AppLogger.Instance().Exception(ex);
throw new FallaTecnicaException(ex.Message, ex);
}
}
I'm working with EF Core 2.2, .NET core 2.2 and Microsoft SQL Azure (RTM) - 12.0.2000.8 .
EDIT
Here is the GetByCode method
public async Task<Object> GetByCode(string code)
{
try
{
return await this.DatosUow.Object
.FindByCondition(w => w.code == code)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
AppLogger.Instance().Exception(ex);
throw new DataException(ex.Message, ex);
}
}
This is how the inserts methods looks like
public async Task<Object> Save(Object object)
{
try
{
this.DatosUow.Object.Add(object);
await this.DatosUow.Object.SaveChangesAsync();
return object;
}
catch (Exception ex)
{
AppLogger.Instance().Exception(ex);
throw new DataException(ex.Message, ex);
}
}
And This is how the Update methods looks like
public async Task<Object> Update(Object object)
{
try
{
this.DatosUow.Object.Update(object);
await this.DatosUow.Object.SaveChangesAsync();
return object;
}
catch (Exception ex)
{
AppLogger.Instance().Exception(ex);
throw new DataException(ex.Message, ex);
}
}
I'm using the UOW pattern to manage the access data.
I would like to use the same dbContext to save a collection of Program type objects, but if there is any exception or concurrency exception in any of the program object, I would like to rollback the whole saved collection, and need to notify user about all program objects where concurrency issue occurred. I am using Entity Framework 6.1.
Find the code snippet. I am facing an issue that, if any of program object is having concurrency exception then programContext object is throwing the same exception again even if next record is not having any concurrency issue. Please guide on this if it is wrong then how can we achieve it in EF6.1
//Code
public List<ProgramViewModel> SavePrograms(List<ProgramViewModel> newAndUpdatedPrograms)
{
List<ProgramViewModel> failedPrograms = new List<ProgramViewModel>();
using (ProgramContext programContext = new ProgramContext())
{
using (DbContextTransaction dbProgramTransaction = programContext.Database.BeginTransaction())
{
bool isErrorOccured = false;
foreach (var item in newAndUpdatedPrograms)
{
try
{
Program program = new Program();
program.ProgramID = item.ProgramId;
program.Title = item.Title;
program.ProgramCode = item.ProgramCode;
program.Description = item.Description;
//This is to check whether user is having the latest record or dirty record (Concurency check)
program.RowVersion = System.Convert.FromBase64String(item.RowVersion);
if (program.ProgramID == 0)
programContext.Entry(program).State = System.Data.Entity.EntityState.Added;
else
programContext.Entry(program).State = System.Data.Entity.EntityState.Modified;
programContext.SaveChanges(); //Throws the previous concurrency exception here
}
catch (DbUpdateConcurrencyException ex)
{
isErrorOccured = true;
failedPrograms.Add(item);
}
}
if (isErrorOccured)
{
dbProgramTransaction.Rollback();
}
else
{
dbProgramTransaction.Commit();
}
}
}
return failedPrograms;
}
i have following peace of code:
IAsyncResult beginExecuteReader = command.BeginExecuteNonQuery();
while (!beginExecuteReader.IsCompleted)
{
if (controllerTask.CancellationTokenSource.IsCancellationRequested)
{
command.Cancel();
}
Thread.Sleep(100);
}
try
{
result = command.EndExecuteNonQuery(beginExecuteReader);
}
catch (SqlException exception)
{
if (exception.ErrorCode == OperationCanceled)
{
throw new OperationCanceledException();
}
throw;
}
How can i identify, that catched exception is caused by operation cancelation. In this case ExecuteNonQuery throws exception with error code 0x80131904, but it's very general exception which can be caused by many reasons. Error message looks like this: {"A severe error occurred on the current command. The results, if any, should be discarded.\r\nOperation cancelled by user."}
I don't see any options except of parsing of error message... Any ideas?
Thanks
PS. Yeah, i know that Cancel command for asyncronyc operation probably is not the best idea, because for .NET 2.0 there was warning on MSDN, but for .NET 4.0 this warning is removed. And i also don't like another implementations when cancel method is called from another thread, as for me it makes code more difficult
There doesn't seem to be a locale insensitive mechanism to catch just this error. The HResult 0x80131904 is just COR_E_SqlException. The error is initiated at TdsParser.cs:2332 without any unique properties. It is almost the exact same code as :2759 - Unknown Error and :3850 - Unexpected Collation.
Here are the bad solutions I have come up with:
Option 1: Break the good advice of "don't make logic locale sensitive"
using (var con = new SqlConnection("Server=(local);Integrated Security=True;"))
{
con.Open();
try
{
var sqc = new SqlCommand("WAITFOR DELAY '1:00:00'", con);
var readThread = Task.Run(() => sqc.ExecuteNonQuery());
// cancel after 5 seconds
Thread.Sleep(5000);
sqc.Cancel();
// this should throw
await readThread;
// unreachable
Console.WriteLine("Succeeded");
}
catch (SqlException ex) when (ex.Number == 0 && ex.State == 0 && ex.Class == 11
&& ex.Message.Contains("Operation cancelled by user."))
{
Console.WriteLine("Cancelled");
}
catch (Exception ex)
{
Console.WriteLine("Error");
}
}
Option 2: Assume that no other severe locally generated error matters after a cancel has been issued
using (var con = new SqlConnection("Server=(local);Integrated Security=True;"))
{
con.Open();
bool isCancelled = false;
try
{
var sqc = new SqlCommand("WAITFOR DELAY '1:00:00'", con);
var readThread = Task.Run(() => sqc.ExecuteNonQuery());
// cancel after 5 seconds
Thread.Sleep(5000);
isCancelled = true;
sqc.Cancel();
// this should throw
await readThread;
// unreachable
Console.WriteLine("Succeeded");
}
catch (SqlException ex) when (isCancelled && ex.Number == 0 && ex.State == 0 && ex.Class == 11)
{
Console.WriteLine("Cancelled");
}
catch (Exception ex)
{
Console.WriteLine("Error");
}
}
So imho you should to do next:
Make a thread where you will use ado (read this Thread example)
Delete Thread.Sleep(100); //Imho never use it
Add to your class static bool muststop=false;
Add to your class public static function to change "muststop"
Change you thread's function to stop it if muststop==true
I hope this help you
You can exam exception message in catch block to find which operation was cancelled by user:
try
{
//your code
}
catch (SqlException ex)
{
if (ex.Message.Contain("Operation cancelled by user"))
{
//Do something here
}
}
string[] usersToAdd = new string[] { "asd", "asdert", "gasdff6" };
using (Entities context = new Entities())
{
foreach (string user in usersToAdd)
{
context.AddToUsers(new User { Name = user });
}
try
{
context.SaveChanges(); //Exception thrown: user 'gasdff6' already exist.
}
catch (Exception e)
{
//Roll back all changes including the two previous users.
}
Or maybe this is done automatically, meaning that if error occurs, committing changes are canceled for all the changes.
is it?
OK
I created a sample a application like the example from the the question and afterwords I checked in the DB and no users were added.
Conclusion: ObjectContext.SaveChange it's automatically a transaction.
Note: I believe transactions will be needed if executing sprocs etc.
I believe (but I am no long time expert in EF) that until the call to context.SaveChanges goes through, the transaction is not started. I'd expect an Exception from that call would automatically rollback any transaction it started.
Alternatives (in case you want to be in control of the transaction) [from J.Lerman's "Programming Entity Framework" O'Reilly, pg. 618]
using (var transaction = new System.Transactions.TransactionScope())
{
try
{
context.SaveChanges();
transaction.Complete();
context.AcceptAllChanges();
}
catch(OptimisticConcurrencyException e)
{
//Handle the exception
context.SaveChanges();
}
}
or
bool saved = false;
using (var transaction = new System.Transactions.TransactionScope())
{
try
{
context.SaveChanges();
saved = true;
}
catch(OptimisticConcurrencyException e)
{
//Handle the exception
context.SaveChanges();
}
finally
{
if(saved)
{
transaction.Complete();
context.AcceptAllChanges();
}
}
}