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

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.

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.

ASP.NET Execute SQL Server stored procedure multiple times async

I am calling a stored procedure via ASP.NET now I am trying to call it 200 times async, I am trying to do this by adding a transaction, however its not working out, here is my code:
try
{
using (connection = new SqlConnection(connectionString))
{
connection.Open();
transaction = connection.BeginTransaction();
for (int i = 0; i < 200; i++)
{
using (SqlCommand command = new SqlCommand("TimeSlotAppointments", connection))
{
command.Transaction = transaction;
command.CommandType = CommandType.StoredProcedure;
SqlParameter parameter1 = command.Parameters.Add("#StartTime", SqlDbType.DateTime);
parameter1.Direction = ParameterDirection.Input;
parameter1.Value = DateTime.Now;
command.ExecuteNonQuery();
}
}
transaction.Commit();
}
}
catch (SqlException e)
{
Console.Write(e);
transaction.Rollback();
}
finally
{
connection.Close();
connection.Dispose();
}
I am passing the current date and time as a parameter and when I check out the results in SQL Server I am expecting the #StartTime to be the same, but they are not, close, but the milliseconds increase for each record, am I going about this the wrong way? What I am trying to accomplish is executing the store procedure 200 times simultaneously.
The start time value is different because you are assigning the value inside the loop and in every iteration, the time has changed (a few milliseconds as you mentioned). If you want to use the same value for all calls, then you need to store the time stamp outside the loop in a variable and use that value in your loop.
This is how your code should look like:
try
{
using (connection = new SqlConnection(connectionString))
{
connection.Open();
transaction = connection.BeginTransaction();
var startTime = DateTime.Now; // I added this line
for (int i = 0; i < 200; i++)
{
using (SqlCommand command = new SqlCommand("TimeSlotAppointments", connection))
{
command.Transaction = transaction;
command.CommandType = CommandType.StoredProcedure;
SqlParameter parameter1 = command.Parameters.Add("#StartTime", SqlDbType.DateTime);
parameter1.Direction = ParameterDirection.Input;
parameter1.Value = startTime; // I changed this line
command.ExecuteNonQuery();
}
}
transaction.Commit();
}
}
catch (SqlException e)
{
Console.Write(e);
transaction.Rollback();
}
finally
{
connection.Close();
connection.Dispose();
}

Select those emails from db if they wanted to get an email

I am developing a system that heavily relies on emailing, I'm trying to determine if users wanted to get notified or not.
User details are stored in SQL Server Express. I want to check which registered users wanted to receive and get their emails from the database. Is this possible?
So far I got this far:
using (SqlCommand command = new SqlCommand())
{
command.Connection = connection;
command.CommandType = CommandType.Text;
command.CommandText = "SELECT COUNT(*) FROM [UserTable] WHERE ([price] = #price)";
command.Parameters.AddWithValue("#price", "10.000");
try
{
connection.Open();
int recordsAffected = command.ExecuteNonQuery();
}
catch (SqlException ex)
{
MessageBox.Show("Error is SQL DB: " + ex);
}
finally
{
connection.Close();
}
}
It returns -1, but I have a 10.000 in one row. And from here I want to save the email addresses of those who has 10.000 on their preferences from the db so I can add it to email list.
So to summarize: Check all rows if some of them has 'yes' and save their 'email' from the same row.
Can someone point me to the right direction? Thank you.
Updated it for #SeM
private void getMailList()
{
using (SqlConnection connection = new SqlConnection("Data Source=DESKTOP-9MMTAI1\\SQLEXPRESS;Initial Catalog=master;Integrated Security=True"))
{
try
{
connection.Open();
using (SqlCommand cmd = connection.CreateCommand())
{
cmd.CommandText = "SELECT COUNT(*) FROM UserTable WHERE price = #price";
cmd.Parameters.Add(new SqlParameter("#price", 10000));
int count = int.Parse(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
MessageBox.Show("Error is SQL DB: " + ex);
//Handle your exception;
}
}
}
ExecuteNonQuery returning the number of rows that affected only for Update, Insert and Delete statements. In your case, you will always get get -1, because on Select statement ExecuteNonQuery returning -1
So try this:
using(SqlConnection connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
using(SqlCommand cmd = connection.CreateCommand())
{
cmd.CommandText = "SELECT COUNT(*) FROM UserTable WHERE price = #price";
cmd.Parameters.Add(new SqlParameter("#price", 10000));
int count = int.Parse(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
//Handle your exception;
}
}
As commented above, ExecuteNonQuery does just that - no query results.
Instead:
int recordsAffected = (int)command.ExecuteScalar();

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

Delete in oracle 10g from asp.net web site - wait on commint?

I need delete data in oracle 10g database from ASP.NET 2.0 web site.
Method DeleteMonthPlan I use on execute delete command. Problem is that this command is executing long time "in browser" and finally delete command is not executed. Maybe it waits on commit? What is root of problem?
This SQL command DELETE C_PPC_PLAN WHERE MFG_MONTH='VALUE' is OK.
MFG_MONTH column type is VARCHAR2(16)
First I need call method DeleteMonthPlan and than I need call InsertDatePlan.
private static void DeleteMonthPlan(string monthIndex)
{
try
{
using (var conn = new OracleConnection(GenerateConnectionString()))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = string.Format("DELETE C_PPC_PLAN WHERE MFG_MONTH='{0}'", monthIndex);
cmd.ExecuteNonQuery();
}
}
catch (Exception exception)
{
throw exception;
}
}
For example this method I use on insert and it is OK.
public void InsertDatePlan(DatePlan dp,
string monthIndex)
{
DeleteMonthPlan(monthIndex);
try
{
using (var conn = new OracleConnection(GenerateConnectionString()))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.Parameters.Add(":Site", OracleType.VarChar).Value = dp.Site;
cmd.Parameters.Add(":Week", OracleType.VarChar).Value = dp.MfgWeek;
cmd.Parameters.Add(":Month", OracleType.VarChar).Value = dp.MfgMonth;
cmd.Parameters.Add(":Year", OracleType.VarChar).Value = dp.MfgYear;
cmd.Parameters.Add(":Input", OracleType.Number).Value = dp.Input;
cmd.Parameters.Add(":Output", OracleType.Number).Value = dp.Output;
cmd.Parameters.Add(":LMUser", OracleType.VarChar).Value = dp.LmUser;
cmd.Parameters.Add(":PartNo", OracleType.VarChar).Value = dp.PartNo;
cmd.Parameters.Add(":PartNoDesc", OracleType.VarChar).Value = dp.PartNoDesc;
cmd.CommandText = string.Format("INSERT INTO C_PPC_PLAN (CREATE_TIME, SITE, MFG_DAY,MFG_WEEK,MFG_MONTH,MFG_YEAR,INPUT,OUTPUT,LM_TIME,LM_USER,PART_NO,PART_NO_DESC)"
+ " VALUES (to_date('{0}', 'dd-mm-yyyy hh24:mi:ss'), :Site ,to_date('{1}', 'dd-mm-yyyy hh24:mi:ss'),:Week,"
+ ":Month,:Year,:Input,:Output,to_date('{2}', 'dd-mm-yyyy hh24:mi:ss'),:LMUser,:PartNo,:PartNoDesc)"
, dp.CreateTime, dp.MfgDate, dp.LmTime);
cmd.ExecuteNonQuery();
}
}
catch (Exception exception)
{
throw exception;
}
}
I tried use transaction. I call this method on the bottom but is never finish it means that part
trans.Rollback(); or conn.Close(); is never executed.
private static void DeleteMonthPlan(string monthIndex)
{
var conn = new OracleConnection(GenerateConnectionString());
conn.Open();
OracleCommand cmd= conn.CreateCommand();
OracleTransaction trans = conn.BeginTransaction(IsolationLevel.ReadCommitted);
cmd.Transaction = trans;
try
{
cmd.CommandText = "DELETE C_PPC_PLAN WHERE MFG_MONTH='6'";
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception e)
{
trans.Rollback();
}
finally
{
conn.Close();
}
}
try
DELETE FROM C_PPC_PLAN WHERE MFG_MONTH='6'
BTW your code uses "literals" in some places instead of bind variables (params) which makes it vulnerable to SQL injection which is a really serious security problem!

Categories

Resources