How To Catch Sql Server Error from Ado.net - c#

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.

Related

SQLHelper Class - Insert/Update method return inserted ID ##identity

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.

Do I need to catch rollback exceptions?

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.

How to use SqlTransaction in C#

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

Should I call Parameters.Clear when reusing a SqlCommand with a transation?

I'm coding a transaction manually in ADO.NET. The example I'm working from reuses the SqlCommand which seem like a fine idea.
However, I have added parameters to my command.
My question is: in the following code, is command.Parameters.Clear() correct? Or am I doing it wrong?
using (var connection = new SqlConnection(EomAppCommon.EomAppSettings.ConnStr))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
SqlCommand command = connection.CreateCommand();
command.Transaction = transaction;
try
{
foreach (var itemIDs in this.SelectedItemIds)
{
command.CommandText = "UPDATE Item SET payment_method_id = #batchID WHERE id in (#itemIDs)";
// IS THE FOLLOWING CORRECT?
command.Parameters.Clear();
command.Parameters.Add(new SqlParameter("#batchID", batchID));
command.Parameters.Add(new SqlParameter("#itemIDs", itemIDs));
command.ExecuteNonQuery();
}
transaction.Commit();
}
catch (Exception ex)
{
MessageBox.Show("Failed to update payment batches, rolling back." + ex.Message);
try
{
transaction.Rollback();
}
catch (Exception exRollback)
{
if (!(exRollback is InvalidOperationException)) // connection closed or transaction already rolled back on the server.
{
MessageBox.Show("Failed to roll back. " + exRollback.Message);
}
}
}
}
Since you're repeatedly executing the same query, it's unnecessary to clear them - you can add the parameters outside the loop and just fill them inside.
try
{
command.CommandText = "UPDATE Item SET payment_method_id = #batchID WHERE id in (#itemIDs)";
command.Parameters.Add(new SqlParameter("#batchID", 0));
command.Parameters.Add(new SqlParameter("#itemIDs", ""));
foreach (var itemIDs in this.SelectedItemIds)
{
command.Parameters["#batchID"].Value = batchID;
command.Parameters["#itemIDs"].Value = itemIDs;
command.ExecuteNonQuery();
}
transaction.Commit();
}
Note - you can't use parameters with IN as you've got here - it won't work.
In this condition you need it as you need set new parameters values, so its correct.
By the way, move
command.CommandText = ".."
outside of the loop too, as it's never changed.

SQL Transaction with Parameters

I am using a SQL Transaction statement to execute a stored procedure. Traditionally, I would use command parameters to insert different variables into the command.
When I tried to use the same method with a Transaction, the procedure would not insert into the database, although the transaction would work without an error.
Here is how I am trying to do it:
SqlConnection db = DataConn.SqlConnection();
db.Open();
SqlTransaction transaction = db.BeginTransaction();
try
{
const string strSql = "procSiteAddMember #uID, #userName, #password, #nickname, #email, #siteAddress";
var sqlComm = new SqlCommand(strSql, db, transaction) {CommandType = CommandType.Text};
sqlComm.Parameters.Add(new SqlParameter("#uID", SqlDbType.VarChar, 255)).Value = uID;
sqlComm.Parameters.Add(new SqlParameter("#userName", SqlDbType.VarChar, 20)).Value =
txtRegisterUsername.Text.Trim();
sqlComm.Parameters.Add(new SqlParameter("#password", SqlDbType.VarChar, 20)).Value =
txtRegisterPassword.Text;
sqlComm.Parameters.Add(new SqlParameter("#nickname", SqlDbType.VarChar, 20)).Value =
txtRegisterNickname.Text.Trim();
sqlComm.Parameters.Add(new SqlParameter("#email", SqlDbType.VarChar, 20)).Value = txtRegisterEmail.Text.Trim();
sqlComm.Parameters.Add(new SqlParameter("#siteAddress", SqlDbType.VarChar, 20)).Value = lblNickname.Text.Trim();
//sqlComm.ExecuteNonQuery();
//DataConn.Disconnect();
transaction.Commit();
Response.Redirect("~/Member/" + txtRegisterNickname.Text);
}
catch (Exception ent)
{
Response.Write("Error: " + ent.Message);
}
I saw This post - But it seems pretty long winded with a lot of variables.
You already solved this but since no one answered I'll do it for future reference.
You still need to execute the query so uncommment your line sqlComm.ExecuteNonQuery();
Also don't forget to add transaction.Rollback(); in your catch block, which you have to put inside another try-catch block in case the Rollback throws an exception.
Example:
try
{
...
transaction.Commit();
}
catch (Exception ex)
{
try
{
...
transaction.Rollback();
}
catch (Exception ex2)
{
...
}
}
For more information visit: https://msdn.microsoft.com/en-us/library/86773566(v=vs.110).aspx

Categories

Resources