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

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.

Related

Using parameter in query results in BEGIN / COMMIT mismatch SQLException

The following code executing a bunch of SQL statements works well...
// SQL Server 2008 R2
SqlConnection connection = null;
var runBatch = false;
try
{
connection = new SqlConnection(connectionString);
connection.Open();
var command = connection.CreateCommand();
// 1st batch
command.CommandText = "BEGIN TRANSACTION";
command.ExecuteNonQuery;
// 2nd batch
command.CommandText = "UPDATE MyTable SET MyColumn = 'foo' WHERE Name = 'bar'";
command.ExecuteNonQuery;
// 3rd batch
command.CommandText = "COMMIT TRANSACTION";
command.ExecuteNonQuery;
}
finally
{
if (connection != null)
{
connection.Close();
}
}
...unless I use a parameter:
// [...]
command.Parameters.AddWithValue("name", "bar");
// 1st batch
command.CommandText = "BEGIN TRANSACTION";
command.ExecuteNonQuery; // <= throws Exception
Exception message:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0
Note: This would work, if I combined all the statements in a single command. But in my app, the original SQL script is a bunch of batches separated by GO which is automatically split into multiple commands (so I have no control over how or what transactions are used):
/* Original SQL */
BEGIN TRANSACTION
GO
UPDATE MyTable
SET MyColumn = 'foo'
WHERE NAME = #name
GO
COMMIT TRANSACTION
GO
I have read about this exception, but nothing really seems to apply to this special scenario.
I have not figured out yet what exact difference introducing a parameter makes here and why it breaks the code. Anyone got a solution for this?
Try this:
// SQL Server 2008 R2
SqlConnection connection = null;
var runBatch = false;
try
{
connection = new SqlConnection(connectionString);
connection.Open();
var command = connection.CreateCommand();
// 1st batch
command.CommandText = "BEGIN TRANSACTION";
command.ExecuteNonQuery();
// 2nd batch
command.CommandText = "UPDATE MyTable SET MyColumn = 'foo' WHERE Name = #name";
command.Parameters.AddWithValue("name", "bar");
command.ExecuteNonQuery();
// 3rd batch
command.CommandText = "COMMIT TRANSACTION";
command.Parameters.Clear();
command.ExecuteNonQuery();
}
finally
{
if (connection != null)
{
connection.Close();
}
}

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

Retrieve number of columns in SQL Table - C#

I'm very new to C#. I'm trying to retrieve the number of columns using:
SELECT count(*) FROM sys.columns
Could you please explain how to use the command and put it into a variable.
To connect to the database you can use the SqlConnection class and then to retrieve the Row Count you can use the Execute Scalar function. An example from MSDN:
cmd.CommandText = "SELECT count(*) FROM sys.columns;";
Int32 count = (Int32) cmd.ExecuteScalar();
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection
You will need to use ExecuteScalar as the others have said. Also, you will need to filter your SELECT on the object_id column to get the columns in a particular table.
SELECT count(*) FROM sys.columns WHERE object_id = OBJECT_ID(N'table_name')
Alternatively, you could do worse than familiarise yourself with the ANSI-standard INFORMATION_SCHEMA views to find the same information in a future-proof, cross-RDBMS way.
You have to use a command and retrieve back the scalar variable :
SqlCommand cmd = new SqlCommand(sql, conn);
Int32 count = (Int32)cmd.ExecuteScalar();
string connectionString =
"Data Source=(local);Initial Catalog=Northwind;"
+ "Integrated Security=true";
// Provide the query string with a parameter placeholder.
string queryString =
"SELECT Count(*) from sys.columns";
// Specify the parameter value.
int paramValue = 5;
// Create and open the connection in a using block. This
// ensures that all resources will be closed and disposed
// when the code exits.
using (SqlConnection connection =
new SqlConnection(connectionString))
{
// Create the Command and Parameter objects.
SqlCommand command = new SqlCommand(queryString, connection);
// Open the connection in a try/catch block.
// Create and execute the DataReader, writing the result
// set to the console window.
try
{
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine("\t{0}",
reader[0]);
}
reader.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
use Executescalar() for getting a single element.
using (SqlConnection con = new SqlConnection(ConnectionString)) //for connecting to database
{
con.Open();
try
{
using (SqlCommand getchild = new SqlCommand("select count(*) from table1 ", con)) //SQL queries
{
Int32 count = (Int32)getchild.ExecuteScalar();
}
}
}
Use ExecuteScalar
Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored.
Int32 colnumber = 0;
string sql = "SELECT count(*) FROM sys.columns";
using (SqlConnection conn = new SqlConnection(connString))
{
SqlCommand cmd = new SqlCommand(sql, conn);
try
{
conn.Open();
colnumber = (Int32)cmd.ExecuteScalar();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
You'll want to use the ADO .NET functions in the System.Data.SqlClient namespace. ExecuteScalar is an easy-to-use method when you only want to get a single result. For multiple results, you can use a SqlDataReader.
using System.Data.SqlClient;
string resultVar = String.Empty;
string ServerName="localhost";
string DatabaseName="foo";
SqlConnection conn=new SqlConnection(String.Format("Data Source={0};Initial Catalog={1};Integrated Security=SSPI",ServerName,DatabaseName));
SqlCommand cmd=new SqlCommand(Query,conn);
try
{
conn.Open();
}
catch (SqlException se)
{
throw new InvalidOperationException(String.Format(
"Connection error: {0} Num:{1} State:{2}",
se.Message,se.Number, se.State));
}
resultVar = (string)cmd.ExecuteScalar().ToString();
conn.Close();

update a mySQL table using C#

I have written some C# to update a MySql table but I get an exception every time I call the method ExecuteNonQuery(). I have researched this on the web and every solution I find produces the same error. I have an open connection to the database and the update query to the database is written correctly. The code that I have so far come up with is :
public int executeUpdate()
{
int result = 0;
if (isConnected)
{
try
{
MySqlConnection cn = new MySqlConnection(connection.ConnectionString);
MySqlCommand cmd = new MySqlCommand();
cmd.Connection = cn;
cmd.CommandText = "UPDATE test SET status_id = 1 WHERE test_id = 1";
int numRowsUpdated = cmd.ExecuteNonQuery();
}
catch (MySqlException exSql)
{
Console.Error.WriteLine("Error - SafeMySql: SQL Exception: " + query);
Console.Error.WriteLine(exSql.StackTrace);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error - SafeMySql: Exception: " + query);
Console.Error.WriteLine(ex.StackTrace);
}
}
else
Console.Error.WriteLine("Error - SafeMySql: executeQuery failed. Not connected to DB");
}
Change your try section to the code below:
try
{
using(MySqlConnection cn = new MySqlConnection(connection.ConnectionString))
{
MySqlCommand cmd = new MySqlCommand();
cmd.Connection = cn;
cmd.CommandText = "UPDATE test SET status_id = 1 WHERE test_id = 1";
cn.Open();
int numRowsUpdated = cmd.ExecuteNonQuery();
cmd.Dispose();
}
}
The connection must be opened before you execute a command. In the example above the command object will immediately be disposed and the connection object will implcitly be closed and disposed when you leave the using section.
I don't see the connection being opened.
Here is an example from MSDN: even inside a using block, they open the connection explicitly
private static void CreateCommand(string queryString,
string connectionString)
{
using (SqlConnection connection = new SqlConnection(
connectionString))
{
SqlCommand command = new SqlCommand(queryString, connection);
command.Connection.Open();
command.ExecuteNonQuery();
}
}
Edit: The principle is the same for MySQL as it is for SQL Server:
public void CreateMySqlCommand(string myExecuteQuery, MySqlConnection myConnection)
{
MySqlCommand myCommand = new MySqlCommand(myExecuteQuery, myConnection);
myCommand.Connection.Open();
myCommand.ExecuteNonQuery();
myConnection.Close();
}

Rollback is not working in 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.

Categories

Resources