Rollback is not working in C# - c#

I am using C# and MYSQL and the Rollback is not working. I have three delete statement where the table third delete SQL don't exists and in catch i am doing rollback but its only happening for third while
string id = dataGridView1.Rows[index].Cells[0].Value.ToString();
string strDelete = "DELETE FROM user WHERE id = " + id;
OdbcTransaction transaction = null;
OdbcCommand cmd = new OdbcCommand();
cmd.Connection = Singleton.Instance.GetConnection();
transaction = Singleton.Instance.GetConnection().BeginTransaction();
cmd.Transaction = transaction;
try
{
cmd.CommandText = strDelete;
cmd.ExecuteNonQuery();
// delete from userdata
strDelete = "DELETE FROM userdata WHERE id = " + id;
cmd.CommandText = strDelete;
cmd.ExecuteNonQuery();
// delete from usersystem
strDelete = "DELETE FROM usersystem WHERE id = " + id;
cmd.CommandText = strDelete;
cmd.ExecuteNonQuery();
// delete from user systemstatus. here table don't exists, will throw
// exception
strDelete = "DELETE FROM usersystemstatusAAAA WHERE id = " + id;
cmd.CommandText = strDelete;
cmd.ExecuteNonQuery();
transaction.Commit();
}
catch (Exception ex)
{
// Attempt to roll back the transaction.
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
Console.WriteLine(" Message: {0}", ex2.Message);
}
}
Here the first two SQL statements are committed while suppose to rollback all.

You need to check the storage type of your MySQL database. Not all storages are transactional. For example InnoDB has transactions while MyISAM does not.
MyISAM versus InnoDB
MyISAM versus InnoDB

I suspect you have a second connection, and there is confusion over the connection/transaction/command; try starting the transaction on the connection you are actually using instead:
cmd.Connection = Singleton.Instance.GetConnection();
transaction = cmd.Connection.BeginTransaction();
also, you might want to look at using blocks here.

Related

C# : there is already an open DataReader associated with this command

I currently have a problem with multiple SqlDataReader and commands in a single method. This method should delete a customer + its related address, networks, ipaddresses...
When I execute the code, I get an error
There is already an open DataReader associated with this Command which must be closed first
So I googled a little bit and read, that the
using(SqlDataReader....)
and adding MultipleActiveResultSets=True to the connection string should help.
I'm using SQL Server 2014, I heard that there is a problem with SQL Server 2005, so that shouldnt be a problem..
But it still doesn't work...
The exception is thrown at
var addressId = (int)command.ExecuteScalar();
Connection string:
Data Source=.\\DATABASE;Initial Catalog=customer;Persist Security Info=True;MultipleActiveResultSets=True;User ID=sa;Password=xxxxxxxx!
Code:
public static Boolean ExecuteDeleteCutomer(string customerId) {
using (SqlConnection connection = new SqlConnection(new DatabaseConnection().ConnectionString)) {
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
SqlDataReader locationReader;
SqlDataReader networkReader;
SqlDataReader ipaddressReader;
// Start a local transaction to delete a customer and related table entries.
transaction = connection.BeginTransaction("StartTransaction DeleteCustomer");
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
try {
//First get the locations of selected customer
command.Parameters.AddWithValue("#customerId", customerId);
command.CommandText =
"SELECT l_id from location where c_id = #customerId";
locationReader = command.ExecuteReader();
using (locationReader) { //save location ids in a reader
while (locationReader.Read()) {
var locationID = locationReader.NextResult();
command.Parameters.AddWithValue("#locationId", locationID);
command.CommandText =
"SELECT a_id from address where l_location = #locationId";
var addressId = (int)command.ExecuteScalar(); // get address ID to delete later
command.Parameters.AddWithValue("#addressId", addressId);
command.CommandText = "SELECT n_id from network where n_location = #locationId";
using (networkReader = command.ExecuteReader()) { // save networks in a reader;
while (networkReader.Read()) {
var networkId = networkReader.NextResult();
command.Parameters.AddWithValue("#networkId", networkId);
command.CommandText = "SELECT ip_id from ipaddress where n_id = #networkId";
using (ipaddressReader = command.ExecuteReader()) { // get ipaddressId ID to delete later
while (ipaddressReader.Read()) {
var ipaddressId = ipaddressReader.NextResult();
command.Parameters.AddWithValue("#ipId", ipaddressId);
command.CommandText = "Delete from ipaddress where ip_id = #ipId; ";
command.ExecuteScalar();
}
}
command.CommandText = "Delete from network where n_id = #networkId; ";
command.ExecuteScalar();
}
}
command.CommandText = "Delete from location where l_id = #locationID; ";
command.ExecuteScalar();
}
}
command.CommandText = "Delete from customer where c_id = #customerId; ";
command.ExecuteScalar();
// Attempt to commit the transaction.
transaction.Commit();
return true;
}
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);
}
return false;
}
}
}
Thanks!
As the error message reads, you already have an open DataReader associated with that Command object.
You need to create a new SqlCommand object for each (nested) command that you want to execute.
SqlCommand locationCommand = connection.CreateCommand();
SqlCommand networkCommand = connection.CreateCommand();
SqlCommand ipAddrCommand = connection.CreateCommand();
Assign your CommandText as needed to each of those command objects, then you can call ExecuteReader on each one and process as needed.
Use a different command instance for the outer and inner commands; so essentially:
var innerCommand = ...
innerCommand.CommandText = "Delete from ipaddress where ip_id = #ipId; ";
var ipId = innerCommand.Parameters.Add(...);
while (ipaddressReader.Read()) {
ipId.Value = ...
innerCommand.ExecuteNonQuery();
}
(and similar for each command)
Basically, each command instance should only be executing once at a time, regardless of whether they share a connection or not.

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

Input data from C# Form

I am having trouble updating data into a table from my C# form. Could someone tell me what I am doing wrong. It does not give me an error and says that it has been submitted successfully. My database show no change however.
try
{
string cmdstring = "UPDATE Test SET COLLECTION_DATE = '#date', SPECIMANID = '#spec', TEST_RESULT = '#result', SUBSTANCE_RESULT = '#substance' WHERE PEOPLESOFT_EMPL_ID = #empID ;";
using (OleDbCommand cmd = new OleDbCommand(cmdstring, con))
{
cmd.Parameters.Add("#date", OleDbType.Date).Value = dateTimePicker1.Value;
cmd.Parameters.Add("#spec", OleDbType.Char).Value = textBoxSpec.Text;
cmd.Parameters.Add("#result", OleDbType.Char).Value = comboBoxResult.Text;
cmd.Parameters.Add("#substance", OleDbType.Char).Value = comboBoxSubstance.Text;
cmd.Parameters.Add("#empID", OleDbType.Char).Value = textBoxID.Text;
con.Open();
cmd.ExecuteNonQuery();
con.Close();
MessageBox.Show("Submitted Successfully");
}
}
catch (Exception ex)
{
MessageBox.Show("Failed due to " + ex.Message);
}
Problem : You are enclosing the Parameters in your update query within single quotes.
Solution : while using parameterised queries you should not enclose the parameters within single quotes, so you need to remove the single quotes around the parameters in update query.
Try This:
string cmdstring = "UPDATE Test SET COLLECTION_DATE = #date, SPECIMANID = #spec,
TEST_RESULT = #result, SUBSTANCE_RESULT = #substance WHERE PEOPLESOFT_EMPL_ID =
#empID ;";
Suggestion: You are displaying the success mesaage without checking the query execution status.
i would suggest you to verify the ExecuteNonQuery() return value before displaying the Success message.
From MSDN :ExecuteNonQuey()
Executes a Transact-SQL statement against the connection and returns
the number of rows affected.
Try This:
int rowsUpdated = cmd.ExecuteNonQuery();
if(rowsUpdated > 0)
MessageBox.Show("Records Inserted Successfully!");
else
MessageBox.Show("Insertion Failed!");

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.

Categories

Resources