Nested TransactionScope() won't roll back - c#

I am trying to make use of nested transactions in C# for the first time. In the past I've always wrapped my SqlCommands inside SqlTransactions inside SqlConnections. Something like this:
using (SqlConnection myCon = new SqlConnection(...))
using (SqlTransaction myTran = myCon.BeginTransaction())
{
using (SqlCommand myCom1 = new SqlCommand("...", myCon, myTran))
{
...
}
using (SqlCommand myCom2 = new SqlCommand("...", myCon, myTran))
{
...
}
.
.
.
myTran.Commit();
}
}
All of this, with the necessary try...catch handling of course so if an exception occurs anywhere inside the SqlTransaction, I know that none of the SqlCommands will be committed.
So I thought I'd give TransactionScope a try but it's not working. Here's what I'm doing; I have two transactions, one after the other, but both inside an outer transaction. Depending on which checkbox had been checked on the form, the code throws an exception either:
1. just before the first inner transaction's commit, or
2. between the two inner transactions, or
3. just before the second inner transaction's commit, or
4. just before the outer transaction's commit
The problem I'm getting is that, regardless of which checkbox I tick, the code executes as if no transactions were present. So in other words, the fact that I bug jump out of the try block because of an exception doesn't roll back any of the transactions.
Would appreciate some help. Below is my code for the little test application.
try
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (TransactionScope transactionOuter = new TransactionScope())
{
using (TransactionScope transactionInner1 = new TransactionScope())
{
using (SqlCommand sqlCommand = new SqlCommand("INSERT INTO BasicTable (Value) VALUES ('Inside Inner Transaction 1')", sqlConnection))
{
sqlCommand.ExecuteNonQuery();
}
if (checkBox_FailInner1.Checked)
throw (new Exception("Failed inside inner transaction 1"));
transactionInner1.Complete();
}
if (checkBox_FailBetween.Checked)
throw (new Exception("Failed between inner transactions"));
using (TransactionScope transactionInner2 = new TransactionScope())
{
using (SqlCommand sqlCommand = new SqlCommand("INSERT INTO BasicTable (Value) VALUES ('Inside Inner Transaction 2')", sqlConnection))
{
sqlCommand.ExecuteNonQuery();
}
if (checkBox_FailInnner2.Checked)
throw (new Exception("Failed inside inner transaction 2"));
transactionInner2.Complete();
}
if (checkBox_FailOuter.Checked)
throw (new Exception("Failed before outer transaction could commit"));
transactionOuter.Complete();
}
}
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
MessageBox.Show("Done");

A SqlConnection enlists itself when it is opened. Only this point in time matters. The connection will be enlisted into whatever transaction is active at that time. You are opening the connection before any transaction is installed.
I believe you have a 2nd misunderstanding: It is not possible to nest transactions. You can only nest scopes. When you rollback an inner scope, the whole transaction is rolled back. All a TransactionScope does is set and manipulate Transaction.Current. Nothing more. The outermost scope installs a transaction. The inner scopes are mostly no-ops. All they do is provide a way to doom the transaction by not completing them.

Related

TransactionScope doesn't rollback a query

I've 2 queries that must work together. First is an Update and second is an Insert query. I placed them into TransactionScope scope:
using (TransactionScope scope = new TransactionScope())
{
SqlCommand updateCmd = new SqlCommand("UPDATE V_Stock SET Quantity= 5 WHERE Id=" + shelfId, con);
SqlCommand insertCmd = new SqlCommand("INSERT INTO V_Stock (Code, Quantity, Name) VALUES (#1, #2, #3)", con);
insertCmd.Parameters.AddWithValue("#1", "Code1");
insertCmd.Parameters.AddWithValue("#2", 15);
insertCmd.Parameters.AddWithValue("#3", "Name1");
try
{
updateCmd.ExecuteNonQuery();
insertCmd.ExecuteNonQuery();
}
catch (Exception ex)
{
scope.Complete();
string s = ex.ToString();
}
}
The update query works properly however the insert query doesn't. In this case, I don't want to execute them. They should be executed when only they work properly.
What can I do to work these queries together?
You need to call scope.Complete when you are ready to commit your transaction, not when it fails.
You should also open the connections inside the scope of the TransactionScope so the connection is registered with that scope.
Also I am not sure where your SqlConnection instance is defined. The Microsoft team always recommends you use short lived connections, use them and get rid of them. Let Sql Server handle connection pooling (usually this is on by default) which makes it very cheap to use and throw away sql connections inside your c# code.
Here is some refactored code and I added some documentation that I found on the Microsoft definition for TransactionScope
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection con = new SqlConnection(yourConnectString))
{
// according to the documentation on TransactionScope
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
con.Open();
// I changed this to make it parameterized as well although this had nothing to do with the TransactionScope question
SqlCommand updateCmd = new SqlCommand("UPDATE V_Stock SET Quantity= 5 WHERE Id= #shelfId", con);
updateCmd.Parameters.AddWithValue("#shelfId", shelfId);
SqlCommand insertCmd = new SqlCommand("INSERT INTO V_Stock (Code, Quantity, Name) VALUES (#1, #2, #3)", con);
insertCmd.Parameters.AddWithValue("#1", "Code1");
insertCmd.Parameters.AddWithValue("#2", 15);
insertCmd.Parameters.AddWithValue("#3", "Name1");
updateCmd.ExecuteNonQuery();
insertCmd.ExecuteNonQuery();
// according to the documentation on TransactionScope
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}

TransactionScope has aborted transaction before disposal

When using TransactionScope it apperars that if internally executed code rolled back the transaction than the parent transaction will rollback as well. Which is good for me. But when disposing that scope it throws an exception meaning that transaction was rolled back already and aborted.
So what is the right way to handle that and properly dispose the scope?
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (var conn = GetConnection())
{
string query =
#"some query that may contain transaction itself
or some SP whith transaction included"
using (var command = new SqlCommand(query, conn))
command.ExecuteNonQuery();
}
}
scope.Complete();
} // Exception here
scope.Dispose() may throw TransactionAborted exception even if scope.Complete() has been called. For example some stored procedures a smart enough to handle exceptions and abort transaction inside T-SQL script using T-SQL TRY/CATCH construct w/o throwing exception to the caller.
So I would consider the safest approach I would suggest is as follows:
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
try
{
using (var conn = GetConnection())
{
string query =
#"some query that may contain transaction itself
or some SP whith transaction included"
using (var command = new SqlCommand(query, conn))
command.ExecuteNonQuery();
}
}
catch (SqlException ex)
{
// log SQL Exception, if any
throw; // re-throw exception
}
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
// we can get here even if scope.Complete() was called.
// log TransactionAborted exception if necessary
}
And don't worry about disposing TransactionScope. scope.Dispose performs whatever necessary to clean it up before throwing TransactionAborted exception.
I do not use stored procs or SQL-specific try/catch, so my situation was slightly different but I got the exact same transaction aborted exception mentioned in the post. I found that if I have a SELECT somewhere inside the primary TransactionScope, that will cause the Transaction to commit, although I am not sure why. I had a case where in order to create an object in the database it first checked to make sure the object didn't already exist with a SELECT, and then the abort exception occurred when Dispose was called. I looked at the Inner Exception and it said the Transaction was trying to commit without a begin. I finally tried wrapping my SELECT in a Suppressed TransactionScope, and then it worked. So:
using(TransactionScope tx = new TransactionScope())
{
//UPDATE command for logging that I want rolled back if CREATE fails
using(TransactionScope tx2 = new TransactionScope(TransactionScopeOption.Suppress))
{
// SELECT command
}
//If not exists logic
//CREATE command
} //Used to error here, but not with the SELECT Suppressed
I hope this helps anyone else who might have gotten this exception without using stored procs.
If an exception throws from your inner query the scope.Complete() line 'll not executed.
Please refer the link below.. And I've made some changes to your query also. I hope it should work for you.
Transaction Scope
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (var conn = GetConnection())
{
string query =
#"some query that may contain transaction itself
or some SP whith transaction included"
using (var command = new SqlCommand(query, conn))
command.ExecuteNonQuery();
}
scope.Complete();
}

When does "SqlConnection does not support parallel transactions" happen?

I have a ton of rather working code that's been here for months and today I saw the following exception logged:
System.InvalidOperationException
SqlConnection does not support parallel transactions.
at System.Data.SqlClient.SqlInternalConnection.BeginSqlTransaction(
IsolationLevel iso, String transactionName)
at System.Data.SqlClient.SqlConnection.BeginTransaction(
IsolationLevel iso, String transactionName)
at my code here
and I'd like to investigate why this exception was thrown. I've read MSDN description of BeginTransaction() and all it says is that well, sometimes this exception can be thrown.
What does this exception mean exactly? What is the deficiency in my code that I should be looking for?
You'll get this if the connection already has an uncommitted transaction and you call BeginTransaction again.
In this example:
class Program
{
static void Main(string[] args)
{
using (SqlConnection conn = new SqlConnection("Server=.;Database=TestDb;Trusted_Connection=True;"))
{
conn.Open();
using (var tran = conn.BeginTransaction())
{
using (var cmd = new SqlCommand("INSERT INTO TESTTABLE (test) values ('" + DateTime.Now.ToString() + "')", conn))
{
cmd.Transaction = tran;
cmd.ExecuteNonQuery();
}
using (var tran2 = conn.BeginTransaction()) // <-- EXCEPTION HERE
{
using (var cmd = new SqlCommand("INSERT INTO TESTTABLE (test) values ('INSIDE" + DateTime.Now.ToString() + "')", conn))
{
cmd.Transaction = tran2;
cmd.ExecuteNonQuery();
}
tran2.Commit();
}
tran.Commit();
}
}
}
}
... I get exactly the same exception at the second BeginTransaction.
Make sure the first transaction is committed or rolled back before the next one.
If you want nested transactions, you might find TransactionScope is the way forward.
The same problem occurs when using the 'wrong' method for a transaction, this happened after we upgraded to a newer version of the Entity Framework.
In the past we were using the following method to create a transaction and mixed EF strong typed linq queries with Sql queries, but since the Connection property did not exist anymore, we replaced all db. with db.Database, which was wrong:
// previous code
db.Connection.Open();
using (var transaction = db.Connection.BeginTransaction())
{
// do stuff inside transaction
}
// changed to the following WRONG code
db.Database.Connection.Open();
using (var transaction = db.Database.Connection.BeginTransaction())
{
// do stuff inside transaction
}
Somewhere they changed the behaviour of that transaction method behaviour with a newer version of the Entity Framework and the solution is to use:
db.Database.Connection.Open();
using (var transaction = db.Database.BeginTransaction())
{
// do stuff inside transaction
}
Notice that the transaction is now callen on Database instead of Connection.

Is TransactionScope implicitly applied until explicitly Completed?

Consider the following methods.
DoA()
{
using (TransactionScope scope = new TransactionScope)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(query, connection);
command.ExecuteNonReader();
DoB();
scope.Complete();
}
}
}
DoB()
{
using (TransactionScope scope = new TransactionScope)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(query, connection);
command.ExecuteNonReader();
DoC();
scope.Complete();
}
}
}
DoC()
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(query, connection);
command.ExecuteNonReader();
}
}
If we call DoA(), do the subsequent interactions in DoB() and DoC() run in the context of DoA()'s transaction as it pertains to SQL Server? Does DoC() run in the context of both DoA() and DoB()'s transactions?
(Or am I grossly misunderstanding something?)
All code logically will be a single transaction. The nested scopes don't necessarily create a new transaction (unless you use RequiresNew), so it will be a single transaction. Now, each scope must vote to complete the transaction so in your second scope if you remove the Complete, should cause the entire transaction to rollback.
DoC will be part of the transaction as well; the ambient transaction will detect the new connection and be enlisted automatically.
Please read all of the details here which explain the behavior of enrolling in the ambient transaction and the different options Requires, RequiresNew, and Suppress.
Also note that if your connections don't use EXACTLY same connection string, this will automatically promote the entire transaction to a Distributed Transaction. Just something to watch out for.
Edited per Andy's comments:
It seems as though something like this would occur on the SQL server:
BEGIN TRANSACTION A
-- do A's work
-- B does NOT create a new transaction
-- do B's work
-- do C's work
COMMIT TRANSACTION A
The following occurs if new TransactionScope(TransactionScopeOption.RequiresNew) is used in DoB().
BEGIN TRANSACTION A
-- do A's work
BEING TRANSACTION B
-- do B's work
-- do C's work
COMMIT TRANSACTION B
COMMIT TRANSACTION A

How to handle sql transaction in this scenario?

I am a C# programmer. I want to clear this complex concept.
If there are 2 databases: A and B. Suppose I want to insert records in both but first in A and then in B. Say if while inserting in db B an exception occurs. The situation is that if B crashes, transaction with db A should also be rolled back. What do I have to do?
I know I can use SqlTransaction object with SqlConnectionString class. Can I have some code for this?
Already asked here : Implementing transactions over multiple databases.
Best answer from keithwarren7 :
use the TransactionScope class like this
using(TransactionScope ts = new TransactionScope())
{
//all db code here
// if error occurs jump out of the using block and it will dispose and rollback
ts.Complete();
}
The class will automatically convert to a distributed transaction if necessary.
.
Edit : adding explanations to original answer
You've got a good example in the MSDN : http://msdn.microsoft.com/fr-fr/library/system.transactions.transactionscope%28v=vs.80%29.aspx.
This example shows you how to use 2 Database Connections in one TransactionScope.
// Create the TransactionScope to execute the commands, guaranteeing
// that both commands can commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
try
{
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Create the SqlCommand object and execute the first command.
SqlCommand command1 = new SqlCommand(commandText1, connection1);
returnValue = command1.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
// If you get here, this means that command1 succeeded. By nesting
// the using block for connection2 inside that of connection1, you
// conserve server and network resources as connection2 is opened
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
try
{
// The transaction is escalated to a full distributed
// transaction when connection2 is opened.
connection2.Open();
// Execute the second command in the second database.
returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
catch (Exception ex)
{
// Display information that command2 failed.
writer.WriteLine("returnValue for command2: {0}", returnValue);
writer.WriteLine("Exception Message2: {0}", ex.Message);
}
}
catch (Exception ex)
{
// Display information that command1 failed.
writer.WriteLine("returnValue for command1: {0}", returnValue);
writer.WriteLine("Exception Message1: {0}", ex.Message);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
If you like to have only one connection and like to manage the things then you may using the Linked Server and call the SP from server A which can call the SP from Server B
:)

Categories

Resources