The following code is part of my business layer:
public void IncrementHits(int ID)
{
using (var context = new MyEntities())
{
using (TransactionScope transaction = new TransactionScope())
{
Models.User userItem = context.User.First(x => x.IDUser == ID);
userItem.Hits++;
try
{
context.SaveChanges();
transaction.Complete();
}
catch (Exception ex)
{
transaction.Dispose();
throw;
}
}
}
}
Sometimes (once or twice a week) I get a TransactionInDoubtException. Stacktrace:
at System.Transactions.TransactionStateInDoubt.EndCommit(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
As far as I know, the default isolation level is serializable, so there should be no problem with this atomic operation. (Assuming there is no timeout occuring because of a write lock)
How can I fix my problem?
Use transaction.Rollback instead of transaction.Dispose
If you have a transaction in a pending state always rollback on exception.
msdn says "if the transaction manager loses contact with the subordinate participant after sending the Single-Phase Commit request but before receiving an outcome notification, it has no reliable mechanism for recovering the actual outcome of the transaction. Consequently, the transaction manager sends an In Doubt outcome to any applications or voters awaiting informational outcome notification"
Please look into these links
https://msdn.microsoft.com/en-us/library/cc229955.aspx
https://msdn.microsoft.com/en-us/library/system.transactions.ienlistmentnotification.indoubt(v=vs.85).aspx
Add finally block to dispose the transaction. And set some status = true if you commit the transactions.
Check same in finally block and if status is true then don't rollback otherwise rollback and dispose the transaction.
Would it behave properly if you moved your try/catch to include the using directive for the Transaction scope? The using directive should implicitly call Dispose for you (so doing it explicitly seems redundant) and then you can throw the exception. This is what I see implement in examples from MSDN:
https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.dispose(v=vs.110).aspx
Related
According to Microsoft documentation:
TransactionScope.Complete is merely a way of informing the transaction manager of your status, the actual work of committing the transaction by the transaction manager occurs after the last line of code in the using block. The transaction manager decides to commit or rollback based on whether the TransactionScope.Complete method was called.
So what happens if an exception (such as internet down, database connection closed) occurs in the midst of committing the transactions? Will it rollback or throw a TransactionScope exception?
using (TransactionScope transactionScope = new TransactionScope())
{
WriteToCloudDatabase(input);
WriteToCloudDatabase(input); // I know it will rollback if exception thrown in here.
transactionScope.Complete();
// Will it rollback if exception thrown in here? (while committing transactions)
}
When disposing the TransactionScope.
If the Complete method has been called, the transaction manager will commit the transaction.
If an exception is raised by any code after the Complete method call, as the Complete method has already been called, when the transactionScope is disposed, the transaction manager will commit the transaction.
If the transaction manager fail to commit the transaction due to connection loss, all opened transactions are expected to be rollbacked by the database itself.
Just found the answer: It will rollback and throw a TransactionException.
According to the Microsoft documentation, TransactionScope commits the transactions by calling the CommittableTransaction.Commit method.
https://docs.microsoft.com/en-us/dotnet/framework/data/transactions/implementing-an-implicit-transaction-using-transaction-scope
Let's see what the CommittableTransaction.Commit method do:
When this method is called, all objects that have registered to participate in the transaction are polled and can independently indicate their vote to either commit or roll back the transaction. If any participant votes to roll back the transaction, it is rolled back and this method throws a TransactionException exception. This is a normal occurrence for a transaction and your code should catch and process such exceptions.
Microsoft documentation on CommittableTransaction Class: https://docs.microsoft.com/en-us/dotnet/api/system.transactions.committabletransaction.commit?view=net-5.0#System_Transactions_CommittableTransaction_Commit
try
{
using (TransactionScope transactionScope = new TransactionScope())
{
WriteToCloudDatabase(input);
transactionScope.Complete();
}
}
catch (TransactionException)
{
// If something wrong happens while committing the transaction,
// it will rollback and throw this exception.
}
catch (Exception)
{
// If something wrong happens before committing the transaction,
// it will rollback and that exception will be caught in here.
}
I need to know if my transaction scope was successful or not. As in if the records were able to be saved in the Database or not.
Note: I am having this scope in the Service layer, and I do not wish to include a Try-Catch block.
bool txExecuted;
using (var tx = new TransactionScope())
{
//code
// 1 SAVING RECORDS IN DB
// 2 SAVING RECORDS IN DB
tx.Complete();
txExecuted = true;
}
if (txExecuted ) {
// SAVED SUCCESSFULLY
} else {
// NOT SAVED. FAILED
}
The commented code will be doing updates, and will probably be implemented using ExecuteNonQuery() - this returns an int of the number of rows affected. Keep track of all the return values to know how many rows were affected.
The transaction as a whole will either succeed or experience an exception when it completes. If no exception is encountered, the transaction was successful. If an exception occurs, some part of the transaction failed; none of it took place
By considering these two facts (records affected count, transaction exception or no) you can know if the save worked and how many rows were affected
I didn't quite understand the purpose of txExecuted- if an exception occurs it will never be set and the if will never be considered. The only code that will thus run is the stuff inside if(true). I don't see how you can decide to not use a try/catch and hope to do anything useful with a system that is geared to throw an exception if something goes wrong; you saying you don't want to catch exceptions isn't going to stop them happening and affecting the control flow of your program
To be clear, calling the Complete() method is only an indication that all operations within the scope are completed successfully.
However, you should also note that calling this method does not
guarantee a commit of the transaction. It is merely a way of informing
the transaction manager of your status. After calling this method, you
can no longer access the ambient transaction via the Current property,
and trying to do so results in an exception being thrown.
The actual work of commit between the resources manager happens at the
End Using statement if the TransactionScope object created the
transaction.
Since you are using ADO.NET, ExecuteNonQuery will return the number of rows affected. You can do a database lookup after the commit and outside of the using block.
In my opinion, its a mistake not to have a try/catch. You want to catch the TransactionAbortedException log the exception.
try
{
using (var scope = new TransactionScope())
{
using (var conn = new SqlConnection("connection string"))
{
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
// log
}
I am working on a c# project with EntityFramework and the last developer wrote that:
using (System.Transactions.TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
try
{
//... do somehting
context.SaveChanges();
//... do some other work
context.SaveChanges();
scope.Complete();
}
catch (Exception ex)
{
context.RollbackChanges();
scope.Complete();
}
}
I don't understand why he is using TransactionScope. I tried to throw an exception between the 2 SaveChanges and it didnt rollback the first call modification.
Why using TransactionScope?
Thanks
This should explain it for you - https://blogs.msdn.microsoft.com/alexj/2009/01/11/savechangesfalse/
Edit: as per szer's request.
They key take-away from the link is :
"If you call SaveChanges() or SaveChanges(true),the EF simply assumes that if its work completes okay, everything is okay, so it will discard the changes it has been tracking, and wait for new changes."
You need to call the Rollback() method on the scope object. To create the scope, call context.Database.BeginTransaction():
using (var scope = context.Database.BeginTransaction())
{
try
{
// do some stuff
context.SaveChanges();
// do other stuff
context.SaveChanges();
scope.Commit();
}
catch (Exception)
{
scope.Rollback();
}
}
Like Marc explained you need to roll back the TransactionScope, not the EF context transaction. TransactionScope creates an ambient transaction that operations can enlist in. So what you're doing here is creating a transaction that wraps multiple transactions and can commit or rollback all of them at once.
What's happening in your code is this:
You're committing query 1 (with the SaveChanges call).
You're committing query 2 (with the SaveChanges call).
Error occurs.
You're rolling back the latest transaction (query 2). Note that query 1 is still committed.
You're committing the ambient transaction, 'saving' the results. This means that the final result is that query 1 was committed and query 2 was rolled back.
We were trying to do some integrity checks on our database state for diagnostic reasons, so we wrapped our modification ORM queries in a TransactionScope coupled with a second query that ran diagnostics - something like this:
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, _maxTimeout))
{
ORM.DeleteItem();
ORM.CheckIntegrity();
scope.Complete();
}
It's a hand-rolled ORM, and both those calls end up doing their bit in a nested transaction scope down at the bottom. In other words, when you dig down, DeleteItem() has
using (TransactionScope newScope = new TransactionScope(TransactionScopeOptions.Required, _maxTimeout)
{...}
and CheckIntegrity() also has the same.
For the most part it's been working fine, but I've run across an odd condition. When someone puts in some bad inputs to the query, the DeleteItem() call can throw an exception. That exception is completely caught and handled at a stack level below the wrapper. I believe that exception is also thrown before it gets to nesting the TransactionScope.
But when we get down to the nested scope creation in the CheckIntegrity() call, it throws a "Transaction was aborted error" from the CreateAbortingClone constructor. The inner exception is null.
Most every other mention of the CreateAbortingClone interaction has to do with DTC promotion (or failure thereof) and the inner exception reflects that.
I'm inferring that the abort exception on the CheckIntegrity() call is due to the fact that the DeleteItem() had thrown an exception - even though it was swallowed.
A) is that a correct inference? Is a TransactionScope sensitive to any exceptions thrown, handled or not?
B) is there any way to detect that before making the CheckIntegrity() call? I mean other than re-doing our ORM to let the exception percolate up or adding some other global flag?
Thanks
Mark
I only know how this works with EF(entity framework)
using (var context = new MyContext(this._connectionString))
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
}
}
Then the Transaction is linked to the context. I am not formilar on how your code makes that connection, but may be some fancy build in stuff.
Then it is best to wrap this in a try/catch
try
{
// do-stuff
context.SaveChanges();
//NB!!!!!!
//----------------------
dbContextTransaction.Commit();
}
catch (Exception ex)
{
dbContextTransaction.Rollback();
//log why it was rolled back
Logger.Error("Error during transaction,transaction rollback", ex);
}
so final code would look like
using (var context = new MyContext(this._connectionString))
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
// do-stuff //
context.SaveChanges();
///////////////////////
//if any exception happen, changes wont be saved unless Commit is called
//NB!!!!!!
//----------------------
dbContextTransaction.Commit();
}
catch (Exception ex)
{
dbContextTransaction.Rollback();
//log why it was rolled back
Logger.Error("Error during transaction,transaction rollback", ex);
}
}
}
I was going through a piece of code and came across the following:
using(var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionScopeOptions { IsolationLevel = IsolationLevel.Snapshot })
{
List<Task> tasks = new List<Task>();
try
{
// Perform some database operation to read data (These operations are happening with a transaction scope having scopeoption as "Required" and isolationlevel as "ReadCommitted")
// Filter the data
// At this point the code already has a reference to a WCF duplex callback
// Create a List<Task> and a
foreach(var data in List<SomeData>)
{
var task = Task.Factory.StartNew(() => {
**(WCF Duplex Callback Instance).Process(data);**
});
tasks.Add(task);
}
}
catch(Exception ex)
{
// Log exception details
}
transactionScope.Complete();
}
try
{
Task.WaitAll(tasks);
}
catch(AggregateException ae)
{
ae.Handle( ex => {
// log exception details
return true;
});
}
Questions:
The parent transaction isolation level is "Snapshot" while the inner database reads are using "ReadCommitted". What will be the actual transaction isolation level?
Let's say there are two tasks. Task 1 processes just fine and sends to the WCF client on the callback channel. But task 2 raises an exception. I guess at this time all the activities performed within the parent transaction scope should rollback. But I'm not sure what it means to rollback a set of data already sent over the WCF callback channel that has reached the client.
1) It depends, if you mean nested TransactionScope's then according to MSDN you cannot have them nested with different isolation level:
When using nested TransactionScope objects, all nested scopes must be
configured to use exactly the same isolation level if they want to
join the ambient transaction. If a nested TransactionScope object
tries to join the ambient transaction yet it specifies a different
isolation level, an ArgumentException is thrown
However if you are using some stored procedures, functions or just running raw SQL you may explicitly change the isolation level and it remains set for that connection until it is explicitly changed again. But please note it will not be propagated back to TransactionScope object.
2) It means that all changes done via a resource manager will be rollbacked. Of course if you just query a database and transfer the results back over a channel there is nothing to rollback but if you update a database for example the changes should be rollbacked in this case.
Hope it helps!