Related but not the same to How to catch exception on RollBack
If we catch and explicitly rollback, it appears we need to try/catch wrap the rollback call. Would eliminating the try/catch entirely still rollback, and if the rollback fails, still send up the root cause exception instead of the rollback exception? I've tried to figure out how to replicate this, but I have no idea how to force a rollback timeout.
This is the legacy pattern:
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction())
{
try
{
//do stuff
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}
}
}
This is a pattern I myself have used and seen throughout my career. Recently I experienced a situation where we had a exception occur in production and the stack trace showed the Rollback timing out instead of the actual exception that occurred. I am seeing from my analysis that it is a better practice to not use the explicit Rollback in the catch but instead to let the using statement handle it.
This allows for the correct root cause exception to bubble up and the transaction will be rolled back on the server. To replicate the Rollback timeout I create a table and a procedure and called the stored procedure in a transaction from a Unit Test.
/****** Object: Table [dbo].[Table_1] Script Date: 10/24/2014 12:07:42 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Table_1](
[id] [int] NULL,
[GuidTest] [uniqueidentifier] NULL,
[GuidTest2] [uniqueidentifier] NULL,
[GuidTest3] [uniqueidentifier] NULL
) ON [PRIMARY]
/****** Object: StoredProcedure [dbo].[Test_RollBack] Script Date: 10/24/2014 12:08:04 PM ******/
/****** Object: StoredProcedure [dbo].[Test_RollBack] Script Date: 10/24/2014 12:08:04 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[Test_RollBack]
AS
BEGIN
DECLARE #counter int = 1
while #counter < 3000000
BEGIN
INSERT INTO Table_1(id, GuidTest, GuidTest2, GuidTest3)
VALUES(#counter, newId(), newId(), newId())
set #counter = #counter + 1
END
update Table_1
SET GuidTest = newid()
END
GO
[TestMethod()]
public void RollBackTestTimeout()
{
using (SqlConnection conn = new SqlConnection("Your ConnectionString"))
{
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction())
{
using (SqlCommand cmd = new SqlCommand())
{
try
{
cmd.Connection = conn;
cmd.Transaction = trans;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Test_RollBack";
cmd.ExecuteNonQuery();
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}
}
}
}
}
[TestMethod()]
public void RollBackTestTimeout_WithUsing()
{
using (SqlConnection conn = new SqlConnection("Your ConnectionString"))
{
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction())
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.Transaction = trans;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Test_RollBack";
cmd.ExecuteNonQuery();
trans.Commit();
}
}
}
}
For me the RollBackTestTimeout test Method throws a SqlCommandTimeout but reports a Rollback timeout and RollBackTestTimeout_WithUsing actually shows the Root Cause Exception. So from what I have found I would say let the using handle so you can debug your problem in production later.
Related
I have an SQL helper Class for my application, everything works nice as it should be, but in some code, I need to get the inserted ID using ##identity, what is the best way to do this ??
Here is My method in my SQL helper class :
public static void InsertUpdate_Data(string sql, CommandType cmdType, params SqlParameter[] parameters)
{
using (SqlConnection connStr = new SqlConnection(ConnectionString))
using (SqlCommand cmd = new SqlCommand(sql, connStr))
{
cmd.CommandType = cmdType;
cmd.Parameters.AddRange(parameters);
try
{
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
//log to a file or Throw a message ex.Message;
MessageBox.Show("Error: " + ex.Message);
}
}
}
And this is how I use it :
DBConn.InsertUpdate_Data("customer_add", CommandType.StoredProcedure,
new SqlParameter[]
{
new SqlParameter("#name", txt_name.Text.Trim()),
new SqlParameter("#gender", Gender_comb.Text),
new SqlParameter("#b_o_date", DOBTimePicker1.Value ),
new SqlParameter("#phone", Phone_txt.Text.Trim()),
new SqlParameter("#address", string.IsNullOrEmpty(Location_txt.Text) ? (object)DBNull.Value : Location_txt.Text.Trim()),
new SqlParameter("#note", string.IsNullOrEmpty(Note_txt.Text) ? (object)DBNull.Value : Note_txt.Text.Trim())
}
And also what is the best way to use SQL transactions in some code.
Thank you.
Don't use ##IDENTITY, it's unreliable.
The stored procedure should have, on the line immediately following the insert, SELECT SCOPE_IDENTITY(). Then you can use cmd.ExecuteScalar as mentioned.
For transactions, you have two options.
Either use conn.BeginTransaction() and don't forget to open the connection first, add transaction to command.Transaction, and put the transaction in a using block:
public static int InsertUpdate_Data(string sql, CommandType cmdType, params SqlParameter[] parameters)
{
try
{
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open()
using (var tran = conn.BeginTransaction())
using (SqlCommand cmd = new SqlCommand(sql, connStr, tran))
{
cmd.CommandType = cmdType;
cmd.Parameters.AddRange(parameters);
var result = (int)cmd.ExecuteScalar();
tran.Commit();
return result;
}
}
}
catch (SqlException ex)
{
//log to a file or Throw a message ex.Message;
MessageBox.Show("Error: " + ex.Message);
throw;
}
}
The other, possibly better, option is to use BEGIN TRANSACTION; and COMMIT TRANSACTION; in the procedure. Don't bother with TRY/CATCH/ROLLBACK, just put at the top of the procedure SET XACT_ABORT ON;, this guarantees a rollback on the event of an error.
Other notes:
Use proper types, length and precision on your SqlParameters, it will help with performance.
Do not block the thread with things like MessageBox while the connection is open. Log the exception and check it after. Or better, do what I did above and try/catch around the connection.
Transaction (Process ID 588) was deadlocked on lock resources with
another process and has been chosen as the deadlock victim. Rerun the
transaction.
i get that error when i try to update data from datagridview how can i solve it or what is the problem with my update code and thank you ,
private void Button2_Click(object sender, EventArgs e)
{
using (SqlConnection con = new SqlConnection("***"))
{
con.Open();
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
// INSERT command:
using (SqlCommand com = new SqlCommand("UPDATE tabl2 SET TEL8=#TEL8 WHERE id=#id and CIVILIDD=#CIVILIDD ", con))
{
com.Parameters.AddWithValue("#id", dataGridView1.Rows[i].Cells[0].Value);
com.Parameters.AddWithValue("#CIVILIDD", dataGridView1.Rows[i].Cells[1].Value);
com.Parameters.AddWithValue("#TEL8", dataGridView1.Rows[i].Cells[2].Value.ToString());
com.ExecuteNonQuery();
}
}
MessageBox.Show("Successfully UPDATE....");
}
}
sql server table :
id = int
CIVILIDD = bigint
TEL8 = nvarchar (MAX)
try this if this will work
using (SqlCommand com = new SqlCommand("UPDATE tabl2 SET TEL8=#TEL8 WHERE id=#id and CIVILIDD=#CIVILIDD ", con))
{
com.Parameters.Add("#id", SqlDbType.Int);
com.Parameters.Add("#CIVILIDD", SqlDbType.BigInt);
com.Parameters.Add("#TEL8", SqlDbType.NVarChar);
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
com.Parameters["#id"].Value = dataGridView1.Rows[i].Cells[0].Value);
com.Parameters["#CIVILIDD"].Value = dataGridView1.Rows[i].Cells[1].Value);
com.Parameters["#TEL8"].Value = dataGridView1.Rows[i].Cells[2].Value.ToString());
}
com.ExecuteNonQuery();
}
or
alter your db to single_user before running the update then reverting it back to multi_user
new SqlCommand("Alter Database {databaseName} SET SINGLE_USER With ROLLBACK IMMEDIATE", con);
//your update commands
new SqlCommand("Alter Database {databaseName} SET MULTI_USER With ROLLBACK IMMEDIATE", con);
also for this to occur,
check all sql connection and command creation, if all of them are using using SqlCommand and using SqlConnection. I believe this might be the reason for it to fail as a connection and command are not yet dispose.
maybe a very busy server, research on commandtimeout and connectiontimeout, commandtimeout setting will cause your query to wait up until the query is executed if set to 0. connectiontimeout will cause your connection attempt to wait up until it has made a connection if set to 0.
I have the following code:
using (TransactionScope tran = new TransactionScope())
{
try
{
OracleConnection _transactionDB = new OracleConnection("ConnectionString");
_transactionDB.Open();
OracleCommand _command = new OracleCommand();
_command.Connection = _transactionDB;
_command.CommandType = CommandType.Text;
_command.CommandText = "INSERT INTO table (id, text) VALUES (3, 'test')";
int rowsAffected = _command.ExecuteNonQuery();
OracleCommand _command2 = new OracleCommand();
_command2.Connection = _transactionDB;
_command2.CommandType = CommandType.Text;
_command2.CommandText = "INSERT INTO log (id, text) VALUES (3, 'Success')";
int rowsAffected2 = _command2.ExecuteNonQuery();
//...some other actions(DB changes)
}
}
Is there a solution to commit the second insert immediately, doesn't matter if the transactionscope fails or not? This insert should always be visible in the database, to easier see what was going on in this transaction.
In oracle there is a 'AUTONOMOUS_TRANSACTION Pragma', which is like the function I need, in C#.
Thanks,
Michael
Maybe this will solve you problem:
Write a Oracle PL/SQL Procedure to write the log. This procedure must have the pragma "AUTONOMOUS_TRANSACTION". Then call this procedure instead of inserting directly.
I am using the following code to execute two commands at once. I used SqlTransaction to assure either all command get executed or rolled back. When I run my program without "transaction", it runs properly; but when I use "transaction" with them, they show error.
My code:
SqlTransaction transaction = connectionsql.BeginTransaction();
try
{
SqlCommand cmd1 = new SqlCommand("select account_name from master_account where NOT account_name = 'BANK' AND NOT account_name = 'LOAN'", connectionsql);
SqlDataReader dr1 = cmd1.ExecuteReader();
while (dr1.Read())
{
comboBox1.Items.Add(dr1[0].ToString().Trim());
}
cmd1.Dispose();
dr1.Dispose();
SqlCommand cmd2 = new SqlCommand("select items from rate",connectionsql);
SqlDataReader dr2 = cmd2.ExecuteReader();
while (dr2.Read())
{
comboBox2.Items.Add(dr2[0].ToString().Trim());
}
cmd2.Dispose();
dr2.Dispose();
transaction.Commit();
dateTimePicker4.Value = dateTimePicker3.Value;
}
catch(Exception ex)
{
transaction.Rollback();
MessageBox.Show(ex.ToString());
}
Error:
You have to tell your SQLCommand objects to use the transaction:
cmd1.Transaction = transaction;
or in the constructor:
SqlCommand cmd1 = new SqlCommand("select...", connectionsql, transaction);
Make sure to have the connectionsql object open, too.
But all you are doing are SELECT statements. Transactions would benefit more when you use INSERT, UPDATE, etc type actions.
The following example creates a SqlConnection and a SqlTransaction. It also demonstrates how to use the BeginTransaction, Commit, and Rollback methods. The transaction is rolled back on any error, or if it is disposed without first being committed. Try/Catch error handling is used to handle any errors when attempting to commit or roll back the transaction.
private static void ExecuteSqlTransaction(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
// Start a local transaction.
transaction = connection.BeginTransaction("SampleTransaction");
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
try
{
command.CommandText =
"Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')";
command.ExecuteNonQuery();
command.CommandText =
"Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')";
command.ExecuteNonQuery();
// Attempt to commit the transaction.
transaction.Commit();
Console.WriteLine("Both records are written to database.");
}
catch (Exception ex)
{
Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
// Attempt to roll back the transaction.
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
// This catch block will handle any errors that may have occurred
// on the server that would cause the rollback to fail, such as
// a closed connection.
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
}
}
}
See SqlTransaction Class
You can create a SqlTransaction from a SqlConnection.
And use it to create any number of SqlCommands
SqlTransaction transaction = connection.BeginTransaction();
var cmd1 = new SqlCommand(command1Text, connection, transaction);
var cmd2 = new SqlCommand(command2Text, connection, transaction);
Or
var cmd1 = new SqlCommand(command1Text, connection, connection.BeginTransaction());
var cmd2 = new SqlCommand(command2Text, connection, cmd1.Transaction);
If the failure of commands never cause unexpected changes don't use transaction.
if the failure of commands might cause unexpected changes put them in a Try/Catch block and rollback the operation in another Try/Catch block.
Why another try/catch? According to MSDN:
Try/Catch exception handling should always be used when rolling back a transaction. A Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.
Here is a sample code:
string connStr = "[connection string]";
string cmdTxt = "[t-sql command text]";
using (var conn = new SqlConnection(connStr))
{
conn.Open();
var cmd = new SqlCommand(cmdTxt, conn, conn.BeginTransaction());
try
{
cmd.ExecuteNonQuery();
//before this line, nothing has happened yet
cmd.Transaction.Commit();
}
catch(System.Exception ex)
{
//You should always use a Try/Catch for transaction's rollback
try
{
cmd.Transaction.Rollback();
}
catch(System.Exception ex2)
{
throw ex2;
}
throw ex;
}
conn.Close();
}
The transaction is rolled back in the event it is disposed before Commit or Rollback is called.
So you don't need to worry about app being closed.
Well, I don't understand why are you used transaction in case when you make a select.
Transaction is useful when you make changes (add, edit or delete) data from database.
Remove transaction unless you use insert, update or delete statements
Update or Delete with sql transaction
private void SQLTransaction() {
try {
string sConnectionString = "My Connection String";
string query = "UPDATE [dbo].[MyTable] SET ColumnName = '{0}' WHERE ID = {1}";
SqlConnection connection = new SqlConnection(sConnectionString);
SqlCommand command = connection.CreateCommand();
connection.Open();
SqlTransaction transaction = connection.BeginTransaction("");
command.Transaction = transaction;
try {
foreach(DataRow row in dt_MyData.Rows) {
command.CommandText = string.Format(query, row["ColumnName"].ToString(), row["ID"].ToString());
command.ExecuteNonQuery();
}
transaction.Commit();
} catch (Exception ex) {
transaction.Rollback();
MessageBox.Show(ex.Message, "Error");
}
} catch (Exception ex) {
MessageBox.Show("Problem connect to database.", "Error");
}
}
First you don't need a transaction since you are just querying select statements and since they are both select statement you can just combine them into one query separated by space and use Dataset to get the all the tables retrieved. Its better this way since you made only one transaction to the database because database transactions are expensive hence your code is faster.
Second of you really have to use a transaction, just assign the transaction to the SqlCommand like
sqlCommand.Transaction = transaction;
And also just use one SqlCommand don't declare more than one, since variables consume space and we are also on the topic of making your code more efficient, do that by assigning commandText to different query string and executing them like
sqlCommand.CommandText = "select * from table1";
sqlCommand.ExecuteNonQuery();
sqlCommand.CommandText = "select * from table2";
sqlCommand.ExecuteNonQuery();
I have the following code which calls a stored procedure. I want to be able to trap any error that occurs during the running of the stored procedure.
try {
using (var connection = GetConnection()) {
using (SqlCommand cmd = connection.CreateCommand()) {
connection.Open();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "VerifyInitialization";
cmd.Parameters.Add(new SqlParameter("#userId", user.Id));
cmd.Parameters.Add(new SqlParameter("#domainId", user.DomainId));
cmd.ExecuteNonQueryAsync();
}
}
}
catch (Exception ex) {
throw new LoginException(LoginExceptionType.Other, ex.Message);
}
This is the stored procedure, which basically just calls other stored procedures.
ALTER PROCEDURE [dbo].[VerifyInitialization]
-- Add the parameters for the stored procedure here
#userId int,
#domainId int
AS
BEGIN
Begin Try
SET NOCOUNT ON;
Exec VerifyInitializationOfDefaultLocalizationItems
Exec VerifyInitializationOfLayoutLists #domainId
Exec VerifyInitializationOfLayoutListItems #domainId
Exec VerifyInitializationOfLocalizationItems #domainId
Exec VerifyInitializationOfLookupLists #domainId
Exec VerifyInitializationOfLookupListItems #domainId
End try
Begin Catch
-- Raise an error with the details of the exception
DECLARE
#ErrMsg nvarchar(4000) = Error_message(),
#ErrSeverity int = ERROR_SEVERITY();
RAISERROR(#ErrMsg, #ErrSeverity, 1)
End Catch
End
What do I need to do to catch an error in the Stored Proc that will be returned back to C#? Say for example a field name is renamed which prevents one of the stored procs from running. I don't want it to fail silently.
Greg
Using ExecuteNonQueryAsync() in your case, isn't as good as using ExecuteNonQuery().
try {
using (var connection = GetConnection()) {
using (SqlCommand cmd = connection.CreateCommand()) {
connection.Open();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "VerifyInitialization";
cmd.Parameters.Add(new SqlParameter("#userId", user.Id));
cmd.Parameters.Add(new SqlParameter("#domainId", user.DomainId));
//cmd.ExecuteNonQueryAsync(); - This line should be .ExecuteNonQuery()
cmd.ExecuteNonQueryAsync();
}
}
}
catch (Exception ex) {
throw new LoginException(LoginExceptionType.Other, ex.Message);
}
Another thing you may want to consider is catching a more specific SqlException as opposed to the more general Exception, like this:
catch (SqlException exc) {
throw new SqlException(/* Handle your exception messaging here. */);
}
catch (Exception ex) {
throw new LoginException(LoginExceptionType.Other, ex.Message);
}
EDIT: I posted this answer not realizing that #usr had already answered it in the comments above. I will delete if you like.