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();
}
}
Related
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.
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.
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
We use application in server with Sql server and it receive many SDF file from clients to update the SqlServer Data Base the update for many Tables and I use Transaction for this, the application in server used by multiple user than it possible to be two updates in the same time, and the two with transaction, the application throw an exception that table used by another transaction !!
New transaction is not allowed because there are other threads running in the session.
and the user have other task to do in the app with transaction too, for the moment if there's an update he have to wait until it finish.
Any ideas?
Update : there's some code for my fonction : this is my fonctions with transaction run in BackgroundWorker with timer :
DbTransaction trans = ConnectionClass.Instance.MasterConnection.BeginTransaction();
try
{
ClientDalc.UpdateClient(ChosenClient, trans);
// other functions with the same transaction
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
return false;
}
Update Client:
public static bool UpdateClient(ClientEntity ChosenClient, DbTransaction trans)
{
bool retVal = true;
string sql = "UPDATE ClientTable Set ........";
try
{
using (DbCommand cmd = DatabaseClass.GetDbCommand(sql))
{
cmd.Transaction = trans;
cmd.Parameters.Add(DatabaseClass.GetParameter("#Name", Client.Name));
//
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
retVal = false;
}
return retVal;
}
If I run any thing else in the same time that BackgroundWorker is in progress I got exception even is not a transaction
ExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.”
Is this a totally borked posting?
The only reference I find to that error points to Entity Framework, NOT Sql server and I ahve never seen that in sql server - in fact, sql server has no appropiate conceot for session at all. They are called Conenctions there.
SqlException from Entity Framework - New transaction is not allowed because there are other threads running in the session
In that case your description and the tagging makes ZERO sense and the onyl answer is to use EntityFramework PROPERLY - i.e. not multi threaded. Open separate sessions per thread.
Try to follow the sample C# code
using (SqlConnection con = new SqlConnection("Your Connection String"))
{
using (SqlCommand cmd = new SqlCommand("Your Stored Procedure Name", con))
{
SqlParameter param = new SqlParameter();
param.ParameterName = "Parameter Name";
param.Value = "Value";
param.SqlDbType = SqlDbType.VarChar;
param.Direction = ParameterDirection.Input;
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
}
}
Try to follow sample Stored Procedure
Create Proc YourStoredProcedureName
#ParameterName DataType(Length)
As
Begin
Set NoCount On
Set XACT_ABORT ON
Begin Try
Begin Tran
//Your SQL Code...
Commit Tran
End Try
Begin Catch
Rollback Tran
End Catch
End
Suggestions
Keep your transaction as short as possible.
When you are adding a record in table, it will definitely lock the resource until the transaction is completed. So definitely other users will have to wait.
In case of updation, you can use concurrency controls
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
:)