When does "SqlConnection does not support parallel transactions" happen? - c#

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.

Related

how to rollback all SqlCommands which already executed with SqlTransaction?

I have the following code:
public void Execute(string Query, params SqlParameter[] Parameters)
{
using (var Connection = new SqlConnection(Configuration.ConnectionString))
{
Connection.Open();
using (var Command = new SqlCommand(Query, Connection))
{
if (Parameters.Length > 0)
{
Command.Parameters.Clear();
Command.Parameters.AddRange(Parameters);
}
Command.ExecuteNonQuery();
}
}
}
The method may be called 2 or 3 times for different queries but in same manner.
For example:
Insert an Employee
Insert Employee Certificates
Update Degree of Employee on another table [ Fail can cause here. for example ]
If Point [3] fails, all already committed commands shouldn't execute and must be rolled back.
I know I can put SqlTransaction above and use Commit() method. But what about 3rd point if failed? I think point 3 only will rollback and other point 1,2 will not? How to solve this and what approach should I do??
Should I use SqlCommand[] arrays? What I should I do?
I only find similar question but in CodeProject:
See Here
Without changing your Execute method you can do this
var tranOpts = new TransactionOptions()
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TransactionManager.MaximumTimeout
};
using (var tran = new TransactionScope(TransactionScopeOption.Required, tranOpts)
{
Execute("INSERT ...");
Execute("INSERT ...");
Execute("UPDATE ...");
tran.Complete();
}
SqlClient will cache the internal SqlConnection that is enlisted in the Transaction and reuse it for each call to Execute. So you even end up with a local (not distributed) transaction.
This is all explained in the docs here: System.Transactions Integration with SQL Server
There are a few ways to do it.
The way that probably involves changing the least code and involves the least complexity is to chain multiple SQL statements into a single query. It's perfectly fine to build a string for the Query argument that runs more than one statement, including BEGIN TRANSACTION, COMMIT, and (if needed) ROLLBACK. Basically, keep a whole stored procedure in your C# code. This also has the nice benefit of making it easier to use version control with your procedures.
But it still feels kind of hackish.
One way to reduce that effect is marking the Execute() method private. Then, have an additional method in the class for each query. In this way, the long SQL strings are isolated, and when you're using the database it feels more like using a local API. For more complicated applications, this might instead be a whole separate assembly with a few types managing logical functional areas, where the core methods like Exectue() are internal. This is a good idea anyway, regardless of how you end up supporting transactions.
And speaking of procedures, stored procedures are also a perfectly fine way to handle this. Have one stored procedure to do all the work, and call it when ready.
Another option is overloading the method to accept multiple queries and parameter collections:
public void Execute(string TransactionName, string[] Queries, params SqlParameter[][] Parameters)
{
using (var Connection = new SqlConnection(Configuration.ConnectionString))
using (var Transaction = new SqlTransaction(TransactionName))
{
connection.Transaction = Transaction;
Connection.Open();
try
{
for (int i = 0; i < Queries.Length; i++)
{
using (var Command = new SqlCommand(Queries[i], Connection))
{
command.Transaction = Transaction;
if (Parameters[i].Length > 0)
{
Command.Parameters.Clear();
Command.Parameters.AddRange(Parameters);
}
Command.ExecuteNonQuery();
}
}
Transaction.Commit();
}
catch(Exception ex)
{
Transaction.Rollback();
throw; //I'm assuming you're handling exceptions at a higher level in the code
}
}
}
Though I'm not sure how the params keyword works with an array of arrays... I've just not tried that option, but something along these lines would work. The weakness here is also that it's not trivial to have a later query depend on a result from an earlier query, and even queries with no parameter would still need a Parameters array as a placeholder.
A final option is extending the type holding your Execute() method to support transactions. The trick here is it's common (and desirable) to have this type be static, but supporting transactions requires re-using common connection and transaction objects. Given the implied long-running nature of a transaction, you have to support more than one at a time, which means both instances and implementing IDisposable.
using (var connection = new SqlConnection(Configuration.ConnectionString))
{
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
connection.Open();
transaction = connection.BeginTransaction("Transaction");
command.Connection = connection;
command.Transaction = transaction;
try
{
if (Parameters.Length > 0)
{
command.Parameters.Clear();
command.Parameters.AddRange(Parameters);
}
command.ExecuteNonQuery();
transaction.Commit();
}
catch (Exception e)
{
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
//trace
}
}
}

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();
}
}

Transactions and nested Transactions in C#

I read a lot of articles on Transaction but I want to build my own example of nested Transactions to see how it really works in c#. I already have a good idea about them in SQl but C# is giving me a tough time. SO I came here for an example that can explain how Nested Transactions work.
I tried the following code to check if the inner transaction will be committed or not. Since the TransactionScope property is RequiresNew the inner transaction should execute but I introduced a deliberate Unique key violation in outer Transaction and my inner transaction didn't execute. Why? Is my concept messed up?
Database _Cataloguedatabase = DatabaseFactory.CreateDatabase();
public void TransferAmountSuppress(Account a)
{
var option = new TransactionOptions();
option.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
try
{
using (TransactionScope outerScope = new TransactionScope(TransactionScopeOption.RequiresNew, option))
{
using (DbCommand cmd = _Cataloguedatabase.GetStoredProcCommand(SpUpdateCredit))
{
_Cataloguedatabase.AddInParameter(cmd, CreditAmountParameter, DbType.String, a.Amount);
_Cataloguedatabase.AddInParameter(cmd, CodeParameter, DbType.String, a.Code);
_Cataloguedatabase.ExecuteNonQuery(cmd);
}
using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.RequiresNew, option))
{
using (DbCommand cmd = _Cataloguedatabase.GetStoredProcCommand(SpUpdateDebit))
{
_Cataloguedatabase.AddInParameter(cmd, DebitAmountParameter, DbType.String, a.Amount);
_Cataloguedatabase.ExecuteNonQuery(cmd);
}
innerScope.Complete();
}
outerScope.Complete();
}
}
catch (Exception ex)
{
throw new FaultException(new FaultReason(new FaultReasonText(ex.Message)));
}
}

Nested TransactionScope() won't roll back

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.

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();
}

Categories

Resources