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
}
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.
}
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
Using a try-catch structure i'm trying to figure what to do if an exception is caught in any point of the transaction. Below one sample of code:
try
{
DbContext.ExecuteSqlCommand("BEGIN TRANSACTION"); //Line 1
DBContext.ExecuteSqlCommand("Some Insertion/Deletion Goes Here"); //Line 2
DbContext.ExecuteSqlCommand("COMMIT"); //Line 3
}
catch(Exception)
{
}
If the expection was caught executing 'Line 1' nothing must be done besides alerting the error. If it was caught executing the second line i don't know if i need to try to rollback the transaction that was sucessfully opened and the same occurs in case something went wrong with the third line.
Should i just send a rollback anyway? Or send all the commands straight to the bank in a single method call?
Inside the try-catch there's a loop performing many transactions like the one in the sample (and i need lots of small transactions instead of just a big one so i can reuse the SQL's '_log' file properly and avoid it to grow unnecessarily).
If any of the transactions go wrong i'll just need to delete them all and inform what happen't, but i can't turn that into one big transaction and just use rollback otherwise it will make the log file grow up to 40GB.
Think this will help:
using (var ctx = new MyDbContext())
{
// begin a transaction in EF – note: this returns a DbContextTransaction object
// and will open the underlying database connection if necessary
using (var dbCtxTxn = ctx.Database.BeginTransaction())
{
try
{
// use DbContext as normal - query, update, call SaveChanges() etc. E.g.:
ctx.Database.ExecuteSqlCommand(
#"UPDATE MyEntity SET Processed = ‘Done’ "
+ "WHERE LastUpdated < ‘2013-03-05T16:43:00’");
var myNewEntity = new MyEntity() { Text = #"My New Entity" };
ctx.MyEntities.Add(myNewEntity);
ctx.SaveChanges();
dbCtxTxn.Commit();
}
catch (Exception e)
{
dbCtxTxn.Rollback();
}
} // if DbContextTransaction opened the connection then it will close it here
}
taken from: https://entityframework.codeplex.com/wikipage?title=Improved%20Transaction%20Support
Basically the idea of it is your transaction becomes part of the using block, and within that you have a try/catch with the actual sql. If anything fails within the try/catch, it will be rolled back
As of Entity Framework 6, ExecuteSqlCommand is wrapped with its own transaction as explained here: http://msdn.microsoft.com/en-gb/data/dn456843.aspx
Unless you explicitly need to roll multiple sql commands into a single transaction, there is no need to explicitly begin a new transaction scope.
With respect to transaction log growth and assuming you are targeting Sql Server then setting the transaction log operation to simple will ensure the log gets recycled between checkpoints.
Obviously if the transaction log history is not being maintained across the entire import, there is no implicit mechanism to rollback all the data in case of failure. Keeping it simple, I would probably just add a 'created' datetime field to the table and delete from the table based on a filter to the created field if I needed to delete all rows in case of error.
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);
}
}
}
Here's some background: Periodically our site will crash to the point of having to restart IIS; this almost always happens within an hour of patching a DLL (we use a Web Site project, not a Web Application project, so each ASPX page is a separate DLL).
In doing some research I have found that our homebrew DAL can, while debugging, cause the built-in webserver with Visual Studio to actually stop working and be shut down if it encounters a SQL error in a stored procedure (I mean it will not only throw an exception that is displayed in the browser, it will actually say that the web server has experienced an error and needs to close!)
In digging further the error seems to be related to the use of transactions for everything (including Select statements) in the DAL. What seems to happen is this:
Tries to execute stored procedure, stored procedure fails due to missing/invalid column or other error.
Application code catches the error and rethrows it (bad, yes, but I didn't write this).
Transaction tries to commit despite the exception, gets a NullReferenceException on the transaction.Commit() line (seems to be on the Connection property because there is a transaction object). Also this NullRef seems like it cannot be caught (I tried a demo that force crashed with an invalid Sproc and the NullRef was never caught even though outputting the error gave its type as System.NullReferenceException)
Transaction throws error that say something like "The transaction has completed and is no longer usable".
??? but the VS web server crashes. Debugging this part seems to hang on the above exception, never leaving the method.
Now, I don't know if this is what causes IIS to crash, but it seems quite suspicious and it's a glaring error in any event.
Having not dealt with transactions before and having only the basic idea of them, my first question is why the transaction is still trying to commit after an exception is being thrown? My second question is how to fix the failing commit and presumably infinite looping of exceptions until the server dies. Wouldn't it make sense to add something like this (the method takes a SqlTransaction parameter named transaction):
catch (SqlException se)
{
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
Would that small change fix the constant exception loop that I think is crashing IIS? The DAL itself is extremely brittle and is used concretely in hundreds of files so I can't rewrite it from scratch correctly.
EDIT The entire code block is this (again, legacy code - uses the old microsoft data access block helper):
public static DataSet ExecuteDatasetStoredProc(SqlConnection conn, String storedProcName, SqlTransaction transaction, params SqlParameter[] storedProcParms)
{
try
{
// Execute the stored proc
if (transaction != null)
{
return SqlHelper.ExecuteDataset(transaction, CommandType.StoredProcedure, storedProcName, storedProcParms);
}
else
{
return SqlHelper.ExecuteDataset(conn, CommandType.StoredProcedure, storedProcName, storedProcParms);
}
}
catch (SqlException se)
{
throw new ApplicationException("Error calling " + storedProcName + ". " + se.Message, se);
}
}
However, if the catch block executes the transaction still tries to commit and this seems to be causing the hangups.
also Change your if you wrap your Transactional code in a try catch
Try
{
// your code that you assign and execute the SQl
}
catch (SQLException sqlex)
{
try
{
//try to do the rollback here.. don't always assume the commit or rollback will work
}
catch (Your SQL Exception ex)
{
}
}