Here is the current architecture of my transaction scope source code. The third insert throws an .NET exception (Not a SQL Exception) and it is not rolling back the two previous insert statements. What I am doing wrong?
EDIT: I removed the try/catch from insert2 and insert3. I also removed the exception handling utility from the insert1 try/catch and put "throw ex". It still does not rollback the transaction.
EDIT 2: I added the try/catch back on the Insert3 method and just put a "throw" in the catch statement. It still does not rollback the transaction.
UPDATE:Based on the feedback I received, the "SqlHelper" class is using the SqlConnection object to establish a connection to the database, then creates a SqlCommand object, set the CommandType property to "StoredProcedure" and calls the ExecuteNonQuery method of the SqlCommand.
I also did not add Transaction Binding=Explicit Unbind to the current connection string. I will add that during my next test.
public void InsertStuff()
{
try
{
using(TransactionScope ts = new TransactionScope())
{
//perform insert 1
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /* create parameters for first insert */ };
sh.Insert("MyInsert1", sp);
}
//perform insert 2
this.Insert2();
//perform insert 3 - breaks here!!!!!
this.Insert3();
ts.Complete();
}
}
catch(Exception ex)
{
throw ex;
}
}
public void Insert2()
{
//perform insert 2
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /* create parameters for second insert */ };
sh.Insert("MyInsert2", sp);
}
}
public void Insert3()
{
//perform insert 3
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /*create parameters for third insert */ };
sh.Insert("MyInsert3", sp);
}
}
I have also run into a similar issue. My problem occurred because the SqlConnection I used in my SqlCommands was already open before the TransactionScope was created, so it never got enlisted in the TransactionScope as a transaction.
Is it possible that the SqlHelper class is reusing an instance of SqlConnection that is open before you enter your TransactionScope block?
It looks like you are catching the exception in Insert3() so your code continues after the call. If you want it to rollback you'll need to let the exception bubble up to the try/catch block in the main routine so that the ts.Complete() statement never gets called.
An implicit rollback will only occur if the using is exited without calling ts.complete. Because you are handling the exception in Insert3() the exception never causes an the using statement to exit.
Either rethrow the exception or notify the caller that a rollback is needed (make change the signature of Insert3() to bool Insert3()?)
(based on the edited version that doesn't swallow exceptions)
How long do the operations take? If any of them are very long running, it is possible that the Transaction Binding bug feature has bitten you - i.e. the connection has become detached. Try adding Transaction Binding=Explicit Unbind to the connection string.
I dont see your helper class, but transaction scope rollsback if you don't call complete statement even if you get error from .NET code. I copied one example for you. You may be doing something wrong in debugging. This example has error in .net code and similar catch block as yours.
private static readonly string _connectionString = ConnectionString.GetDbConnection();
private const string inserttStr = #"INSERT INTO dbo.testTable (col1) VALUES(#test);";
/// <summary>
/// Execute command on DBMS.
/// </summary>
/// <param name="command">Command to execute.</param>
private void ExecuteNonQuery(IDbCommand command)
{
if (command == null)
throw new ArgumentNullException("Parameter 'command' can't be null!");
using (IDbConnection connection = new SqlConnection(_connectionString))
{
command.Connection = connection;
connection.Open();
command.ExecuteNonQuery();
}
}
public void FirstMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("#test", "Hello1"));
ExecuteNonQuery(command);
}
public void SecondMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("#test", "Hello2"));
ExecuteNonQuery(command);
}
public void ThirdMethodCauseNetException()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("#test", "Hello3"));
ExecuteNonQuery(command);
int a = 0;
int b = 1/a;
}
public void MainWrap()
{
TransactionOptions tso = new TransactionOptions();
tso.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
//TransactionScopeOption.Required, tso
try
{
using (TransactionScope sc = new TransactionScope())
{
FirstMethod();
SecondMethod();
ThirdMethodCauseNetException();
sc.Complete();
}
}
catch (Exception ex)
{
logger.ErrorException("eee ",ex);
}
}
If you want to debug your transactions, you can use this script to see locks and waiting status etc.
SELECT
request_session_id AS spid,
CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncomitted'
WHEN 2 THEN 'Readcomitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL ,
resource_type AS restype,
resource_database_id AS dbid,
DB_NAME(resource_database_id) as DBNAME,
resource_description AS res,
resource_associated_entity_id AS resid,
CASE
when resource_type = 'OBJECT' then OBJECT_NAME( resource_associated_entity_id)
ELSE 'N/A'
END as ObjectName,
request_mode AS mode,
request_status AS status
FROM sys.dm_tran_locks l
left join sys.dm_exec_sessions s on l.request_session_id = s.session_id
where resource_database_id = 24
order by spid, restype, dbname;
You will see one SPID for two method calls before calling exception method.
Default isolation level is serializable.You can read more about locks and transactions here
I ran into a similar issue when I had a call to a WCF service operation in TransactionScope.
I noticed transaction flow was not allowed due to the 'TransactionFlow' attribute in the service interface. Therefore, the WCF service operation was not using the transaction used by the outer transaction scope. Changing it to allow transaction flow as shown below fixed my problem.
[TransactionFlow(TransactionFlowOption.NotAllowed)]
to
[TransactionFlow(TransactionFlowOption.Allowed)]
Related
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
}
}
}
I'm having an issue getting my transaction scope to rollback while using async/await. Everything works as it should without the transaction scope, but whenever I intentionally cause an exception (duplicate primary key on the insert for 2nd iteration), no rollback (for the update) or any sort of transaction related error occurs.
I should also note that unless "OLE DB Services=-4" is in the connection string, I receive the error:
"The ITransactionLocal interface is not supported by the 'Microsoft.ACE.OLEDB.12.0' provider. Local transactions are unavailable with the current provider."
The code in the button event handler below is just an example for testing the transaction scope. The main goal is to be able to update multiple tables in a loop that's contained in a transaction asynchronously, so I can avoid UI deadlocks and perform rollbacks for any exceptions that may occur during the loop. Any alternatives or suggestions to my problem are appreciated, thanks :)
private async void button1_Click(object sender, EventArgs e)
{
try
{
int customerCount = 150; // First 150 rows of customer table
TransactionScope transaction = null;
using (OleDbConnection dbConn = new OleDbConnection(Provider = Microsoft.ACE.OLEDB.12.0; OLE DB Services=-4; Data Source = " + filePath))
{
dbConn.Open();
using (transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
for (int i = 0; i < customerCount; i++)
{
// Update field indicating customer made an invoice
var taskName = sql.executeAsync("UPDATE Customer SET lastInvoiceDate = #date WHERE customerID = #custID", dbConn,
new OleDbParameter("#date", DateTime.Today),
new OleDbParameter("#custID", i));
// Insert new invoice - Breaks here
var taskInsert = sql.executeAsync("INSERT INTO Invoice VALUES (1, 'thisisatestinvoice', '$100.50')", dbConn);
await Task.WhenAll(taskName, taskInsert);
}
}
// All updates executed properly
transaction.Complete();
}
}
catch (AggregateException exception)
{
foreach (Exception ex in exception.InnerExceptions)
{
MessageBox.Show(ex.Message);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public async Task executeAsync(string dbQuery, OleDbConnection dbConn, params OleDbParameter[] parameters)
{
var dbComm = new OleDbCommand(dbQuery, dbConn);
if (parameters != null)
dbComm.Parameters.AddRange(parameters);
await dbComm.ExecuteNonQueryAsync().ConfigureAwait(false);
}
I wasn't able to get the transaction scope to work, and I'm not entirely sure what the issue is, I think it's due to me being on ACE.OLEDB.12.0, but I found another alternative with OleDbTransaction that will rollback if any failures occur.
private async void button1_Click(object sender, EventArgs e)
{
try
{
using (OleDbConnection dbConn = new OleDbConnection(SQLWrapper.CONNECT_STRING))
{
dbConn.Open();
OleDbTransaction dbTrans = dbConn.BeginTransaction();
var taskName = sql.executeAsync("UPDATE Config SET Busname = #name", dbConn, dbTrans,
new OleDbParameter("#name", "name"));
var taskInsert = sql.executeAsync("INSERT INTO Callout VALUES (16, 'ryanistesting')", dbConn, dbTrans);
await Task.WhenAll(taskName, taskInsert);
dbTrans.Commit();
}
}
}
public async Task executeAsync(string dbQuery, OleDbConnection dbConn, OleDbTransaction dbTrans, params OleDbParameter[] parameters)
{
using (var dbComm = new OleDbCommand(dbQuery, dbConn))
{
if (parameters != null)
dbComm.Parameters.AddRange(parameters);
if (dbTrans != null)
dbComm.Transaction = dbTrans;
await dbComm.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
Maybe you know the answer by now as is' been a while. Same issue happened to me. Thing is that when using ConfigureAwait(false) you are telling the runtime to pick any free thread from the thread pool instead of waiting for the previous used when calling the async method. A different thread is used with a different context, creating another connection under the same transaction scope which was meant for only one connection. Then the transaction gets promoted to a distributed transaction. That was my experience, I had to abandon the idea of using async as it best and wait to for the previos thread to finish by not using configureAwait(false) or Task.WhenAll. Hope that help!
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();
}
I have the following code
try
{
using (var connection = new SqlConnection(Utils.ConnectionString))
{
connection.Open();
using (var cmd = new SqlCommand("StoredProcedure", connection))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
var sqlParam = new SqlParameter("id_document", idDocument);
cmd.Parameters.Add(sqlParam);
int result = cmd.ExecuteNonQuery();
if (result != -1)
return "something";
//do something here
return "something else";
}
}
//do something
}
catch (SqlException ex)
{
return "something AKA didn't work";
}
The question is: Does var connection still get closed if an unexpected error happens between the using brackets ({ })?
The problem is that most of my calls to stored procedures are made this way, and recently I have been getting this error:
System.InvalidOperationException: Timeout expired. The timeout
period elapsed prior to obtaining a connection from the pool. This
may have occurred because all pooled connections were in use and max
pool size was reached.
The other way I access the DB is through nHibernate.
using Statement (C# Reference)
The using statement ensures that Dispose is called even if an
exception occurs while you are calling methods on the object. You can
achieve the same result by putting the object inside a try block and
then calling Dispose in a finally block; in fact, this is how the
using statement is translated by the compiler. The code example
earlier expands to the following code at compile time (note the extra
curly braces to create the limited scope for the object):
Yes, if it gets into the body of the using statement, it will be disposed at the end... whether you reached the end of the block normally, exited via a return statement, or an exception was thrown. Basically the using statement is equivalent to a try/finally block.
Is that the only place you acquire a connection? Has your stored procedure deadlocked somewhere, perhaps, leaving lots of connections genuinely "busy" as far as the client code is concerned?
In terms of your connection pool running out of available connections, if you are in a distributed environment and using many applications to access SQL Server but they all use the same connection string, then they will all be using the same pool on the server. To get around this you can change the connection string for each application by setting the connection WorkstationID to the Environment.MachineName. This will make the server see each connection as different and provide a pool to each machine instead of sharing the pool.
In the below example we even pass in a token to allow an application on the same machine to have multiple pools.
Example:
private string GetConnectionStringWithWorkStationId(string connectionString, string connectionPoolToken)
{
if (string.IsNullOrEmpty(machineName)) machineName = Environment.MachineName;
SqlConnectionStringBuilder cnbdlr;
try
{
cnbdlr = new SqlConnectionStringBuilder(connectionString);
}
catch
{
throw new ArgumentException("connection string was an invalid format");
}
cnbdlr.WorkstationID = machineName + connectionPoolToken;
return cnbdlr.ConnectionString;
}
Replace your above code.. by this.. and check again..
try
{
using (var connection = new SqlConnection(Utils.ConnectionString))
{
connection.Open();
using (var cmd = new SqlCommand("StoredProcedure", connection))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
var sqlParam = new SqlParameter("id_document", idDocument);
cmd.Parameters.Add(sqlParam);
int result = cmd.ExecuteNonQuery();
if (result != -1)
return "something";
//do something here
return "something else";
}
connection.Close();
connection.Dispose();
}
//do something
}
catch (SqlException ex)
{
return "something AKA didn't work";
}
Here's a reference:
http://msdn.microsoft.com/en-us/library/yh598w02(v=vs.80).aspx
What I know is that if you use an object within the using {} clause, that object inherits the IDisposable interface (i.e. SqlConnection inherits DbConnection, and DbConnection inherits IDisposable), which means if you get an exception, any object will be closed and disposed properly.
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
:)