How to handle sql transaction in this scenario? - c#

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
:)

Related

Which one works better? Transaction in SQL Server or in C#?

I write stored procedures with read uncommitted transaction isolation level, but when connection time out the rollback in catch in SQL doesn't work.
When I use SqlTransaction in my business layer in .Net, the problem is solved
and I can support any errors occurred in SQL in my try... catch in .Net.
Is this what I did right?
using (SqlConnection conn = new SqlConnection(Data.DataProvider.Instance().ConnectionString))
{
conn.Open();
SqlTransaction tran = conn.BeginTransaction(IsolationLevel.ReadUncommitted, "Trans");
try
{
object ores = SqlHelper.ExecuteScalar(.......)
string res = ores.ToString();
if (string.IsNullOrEmpty(res))
{
tran.Rollback();
info.TrackingCode = "";
return 0;
}
else if (res == "-4")
{
tran.Rollback();
return -4;
}
else if (res == "-1")
{
tran.Rollback();
return -1;
}
else
{
tran.Commit();
return 1;
}
}
catch (Exception)
{
tran.Rollback();
info.TrackingCode = "";
return 0;
}
As per your requirement, there can be two ways to define SqlTransactions
SQL Server side SqlTransaction
C#.NET (business layer) SqlTransaction.
(Both can not be mixed)
In your case, you tried to define SqlTransaction at the business layer. So better you call Stored-procedure too in the business layer. So the Business layer will rectify the SqlTransaction and time-Out error won't occur.
So first include your Stored-Procedure code as a Command execution (at business layer) and then execute.
Change your code as below with your required conditions.
// Command Objects for the transaction
SqlCommand cmd1 = new SqlCommand("YourStoredProcedureName", cnn);
cmd1.CommandType = CommandType.StoredProcedure;
//If you are using parameter for the Stored-procedure
cmd1.Parameters.Add(new SqlParameter("#Param1", SqlDbType.NVarChar, 50));
cmd1.Parameters["#Param1"].Value = paramValue1;
//Execute stored procedure through C# code
cmd1.ExecuteNonQuery();
transaction.Commit();
EDITED: Reference link
catch (SqlException sqlEx)
{
if (sqlEx.Number == -2)
{
//handle timeout
}
transaction.Rollback();
}
When a client timeout event occurs (.net CommandTimeout for example), the client sends an "ABORT" to SQL Server. SQL Server then simply abandons the query processing. No transaction is rolled back, no locks are released. I solved this problem with Sqltransaction in my .Net Code Instead Sql and Mange Exceptions with SqlExceptions
Both ways are equivalent. But in the case of the business layer, you can handle the transaction through multiple query executions.
Here, you should handle conn.Open() for possible exceptions. Also, check SqlHelper uses your created connection and transaction everywhere in its code.

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

Many transactions in the same tables,

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

Retrying method to call database

Im making a system which should be running 24/7, with timers to control it. There are many calls to the database, and at some point, two methods are trying to open a connection, and one of them will fail. I've tried to make a retry method, so my methods would succeed. With the help from Michael S. Scherotter and Steven Sudit's methods in Better way to write retry logic without goto, does my method look like this:
int MaxRetries = 3;
Product pro = new Product();
SqlConnection myCon = DBcon.getInstance().conn();
string barcod = barcode;
string query = string.Format("SELECT * FROM Product WHERE Barcode = #barcode");
for (int tries = MaxRetries; tries >= 0; tries--) //<-- 'tries' at the end, are unreachable?.
{
try
{
myCon.Open();
SqlCommand com = new SqlCommand(query, myCon);
com.Parameters.AddWithValue("#barcode", barcode);
SqlDataReader dr = com.ExecuteReader();
if (dr.Read())
{
pro.Barcode = dr.GetString(0);
pro.Name = dr.GetString(1);
}
break;
}
catch (Exception ex)
{
if (tries == 0)
Console.WriteLine("Exception: "+ex);
throw;
}
}
myCon.Close();
return pro;
When running the code, the program stops at the "for(.....)", and the exception: The connection was not closed. The connection's current state is open... This problem was the reason why I'm trying to make this method! If anyone knows how to resovle this problem, please write. Thanks
You do
myCon.Open();
inside the for loop, but
myCon = DBcon.getInstance().conn();
outside of it. This way you try to open the same connection multiple times. If you want to protect against loss of DB connection you need to put both inside teh loop
You should move the call to myCon.Open outside the for statement or wrap myCon.Open() checking the connection state before re-opening the connection:
if (myCon.State != ConnectionState.Open)
{
myCon.Open();
}
Edited for new information
How about using Transactions to preserve data integrity, getting on-the-fly connections for multiple access and wrapping them in Using statements to ensure connections are closed? eg
Using (SqlConnection myCon = new SqlConnection('ConnectionString'))
{
myCon.Open();
var transaction = myCon.BeginTransaction();
try
{
// ... do some DB stuff - build your command with SqlCommand but use your transaction and your connection
var sqlCommand = new SqlCommand(CommandString, myCon, transaction);
sqlCommand.Parameters.Add(new Parameter()); // Build up your params
sqlCommand.ExecuteNonReader(); // Or whatever type of execution is best
transaction.Commit(); // Yayy!
}
catch (Exception ex)
{
transaction.RollBack(); // D'oh!
// ... Some logging
}
myCon.Close();
}
This way even if you forget to Close the connection, it will still be done implicitly when the connection gets to the end of its Using statement.
Have you tried adding
myCon.Close();
Into a Finally block. It looks like it is never being hit if you have an exception. I would highly recommend that you wrap the connection, command object etc in Using statements. This will ensure they are disposed of properly and the connection is closed.

TransactionScope not rolling back transaction

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)]

Categories

Resources