Reputation: 10709
Below is the code for two calls to two stored procedures that I'm working on. I thought I had this set up to either do an all or nothing atomic transaction, however if there is an error in the second stored procedure call the first will still execute. I'm still new to writing these in C#, so any help would be appreciated. Thx!
I should also mention something else this is doing that I find odd: If an exception is thrown and the program goes into the "catch" block, it will still run the code in the "finally" block. My understanding was that if an exception is thrown, the code in the "catch" block is all that will be executed.
EDIT Thanks to responses below I've fixed the catch/finally confusion and added an ExecuteNonQuery() call so the first stored procedure gets called as well. However, the first stored procedure, when it is called, needs to be called an executed first before the second stored procedure can do it's work. Is this something that can be accomplished in an atomic transaction or do they have to be run separately?
try
{
cm = Dts.Connections["serverName"];
sqlConn = (SqlConnection)cm.AcquireConnection(Dts.Transaction);
sqlTrans = sqlConn.BeginTransaction("QueueUpdates");
if (dummyIndicator.Equals("Y"))
{
string temp = retrievedMessage.Substring(203, 17);
int newNumber = (int)(long.Parse(temp) / 777);
SqlParameter newNum = new SqlParameter("@newNum", num.Value);
SqlParameter oldNum = new SqlParameter("@oldNum", newNumber);
sqlComm = new SqlCommand("DB.dbo.sp_UpdateNumber", sqlConn, sqlTrans);
sqlComm.CommandType = CommandType.StoredProcedure;
sqlComm.Parameters.Add(newNum);
sqlComm.Parameters.Add(oldNum);
sqlComm.Transaction = sqlTrans;
sqlComm.ExecuteNonQuery();
}
//Update records according to queue messages
sqlComm = new SqlCommand("DB.dbo.sp_AgentIdAprCheck", sqlConn, sqlTrans);
sqlComm.CommandType = CommandType.StoredProcedure;
sqlComm.Parameters.Add(num);
sqlComm.Parameters.Add(addOrUpdate);
sqlComm.Parameters.Add(companyCode);
sqlComm.Parameters.Add(agentID);
sqlComm.Parameters.Add(firstName);
sqlComm.Parameters.Add(lastName);
sqlComm.Parameters.Add(suffix);
sqlComm.Parameters.Add(taxIdType);
sqlComm.Parameters.Add(entityType);
sqlComm.Parameters.Add(corporateName);
sqlComm.Parameters.Add(outNewNumber);
sqlComm.Parameters.Add(outCurrentNumber);
sqlComm.Parameters.Add(outOperator);
sqlComm.Parameters.Add(outDate);
sqlComm.Parameters.Add(returnVal);
sqlComm.ExecuteNonQuery();
sqlTrans.Commit();
if (addOrUpd.Equals("ADD")){recordsAdded++;}
else{recordsUpdated++;}
}
catch (Exception ex)
{
_sqlDataErrors++;
swLog.WriteLine("Message not updated: " + retrievedMessage);
swLog.WriteLine("Error: " + ex.ToString());
sqlTrans.Rollback();
}
finally
{
cm.ReleaseConnection(sqlConn);
}
Upvotes: 2
Views: 953
Reputation: 1936
First, why do you emphasis that transaction
is atomic? Transaction
implies smth that is always atomic, any transaction gurantees that either all of it blocks are commited or not a single block is commited.
Considering the usage of transaction in your case - it depends on your domain logic. Choice is rather simple :
So if you'd provide further explanations of your domain rules and logic, we can say what scenario suites you best.
UPD Ok, after you've explained it a bit, I see no problem - for sure you can implement it that particular way and get the expected behaviour, so that commands from your both sp would be handed by single transaction and act as one. I would like also to point out possible improvement of the code.
You can make exception handling more specific. Right now you are rolling back when anything goes wrong. For example - if Dts.Connections["serverName"];
is null
, you'd get NullReferenceException before sqlTrans
is initialized, and then in catch
block you'd get another NullReferenceException
,dealing with sqlTrans
, which is null
You can split your code into database specific and everything else, just to make sure that your Exception in that block is always exception from database layer. I mean do stuff like
string temp = retrievedMessage.Substring(203, 17);
int newNumber = (int)(long.Parse(temp) / 777);
somewhere else, because it's potentially dangerous and can throw an exception.
Upvotes: 1
Reputation: 9011
Finally block is executed no matter what (either exception occurs or not) Control is always passed to the finally block regardless of how the try block exits.
Move the transaction commit call from the finally block to after ExecuteQuery call. Also you are creating an sql command object in if block for first stored procedure and saving it in sqlComm
but then outside the if block you are creating another sql command object for second stored procedure and saving it in same sqlComm
object. It seems like your first stored procedure is never executed. You might have to change the logic there.
Upvotes: 0
Reputation: 60276
You don't seem to actually execute the first SP, so my guess is the problem in the second one may be due to that?
The purpose of finally
is to always be executed, no matter how the code block ends (normal exit, exceptions, return statements etc. all end up executing the finally
). It should therefore be used for clearing up resources, and not the catch
. Move the Commit()
call to be the last line before the end of the try
block, and remove the ReleaseConnection
from the catch
block, then things should start to work as expected.
Upvotes: 0