How would i use 2 sql commands with 1 reader? - c#

I am trying to Delete a Token from my SQL Database after it has been used.
MySqlCommand cmdSel = new MySqlCommand("SELECT * FROM tokens WHERE token = " + int.Parse(passbox.Text), dbCon);
MySqlDataReader dbRead = cmdSel.ExecuteReader();
if (dbRead.Read())
{
int sqlkey = int.Parse(dbRead["token"].ToString());
if (keyint == sqlkey)
{
using (MySqlCommand delTok = new MySqlCommand("DELETE FROM tokens WHERE token = " + keyint, dbCon))
{
delTok.ExecuteNonQuery(); //MAIN PROBLEM HERE.
/*
MySql.Data.MySqlClient.MySqlException: 'There is already an open DataReader associated with this Connection which must be closed first.'
*/
//ERROR ^^^^^^
}
try
{
dbCon.Close();
loading loading = new loading();
loading.Show();
this.Hide();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}
}
do i have to close the DataReader or is there some way else, and how do i close the reader? i want to Delete the token once the if keyint is sqlkey statement is true/done. the Error only shows once i try to execute the script for the if statement.
The "token" is an int(10)

Connection allows only one open reader.
You can get rid of problem with two datareaders, by executing only one "delete" query.
If token found, query will remove it, if not, query will do nothing.
using (var connection = new MySqlConnection("connection-string"))
using (var command = connection.CreateCommand())
{
command.CommandText = "DELETE FROM tokens WHERE token = #token";
var token = new MySqlParameter
{
ParameterName = "#token",
MySqlDbType = MySqlDbType.Int32,
Value = int.Parse(passbox.Text)
};
command.Parameters.Add(token);
connection.Open();
command.ExecuteNonQuery();
}
Don't try to "keep" connection, simply dispose previous and create new every time you need. ADO.NET in background effectively reuse already opened actual connections.
Use sql parameters for passing values to the query. Sql parameters defend from sql injections and improve sql queries performance, by reusing precompiled query plans.

You have to close the reader first in order to execute another command.
Also, you can't execute a command while Db reader is reading data. So you can use a function or you can execute the command after the reader is closed.
int sqlkey=0;
using(MySqlCommand cmdSel = new MySqlCommand("SELECT * FROM tokens WHERE token = " + int.Parse(passbox.Text), dbCon))
{
MySqlDataReader dbRead = cmdSel.ExecuteReader();
if (dbRead.Read())
{
sqlkey = int.Parse(dbRead["token"].ToString());
}
reader.close();
}
if (keyint == sqlkey)
{
using (MySqlCommand delTok = new MySqlCommand("DELETE FROM tokens WHERE token = " + keyint, dbCon))
{
delTok.ExecuteNonQuery();
try
{
dbCon.Close();
loading loading = new loading();
loading.Show();
this.Hide();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}
}

Made a new private void and called it with the token after the if statement.
private void delQuery(int token)
{
//SETUP CONNECTION
MySqlConnection dbConn = new MySqlConnection("some connection");
//OPEN CONNECTION
dbConn.Open();
//DELETE TOKEN
MySqlCommand delcmd = new MySqlCommand("DELETE FROM tokens WHERE token = " + token, dbConn);
MySqlDataReader dbReader = delcmd.ExecuteReader();
dbReader.Read();
//CLOSE CONNECTION
dbConn.Close();
}
called it using:
if (dbRead.Read())
{
int sqlkey = int.Parse(dbRead["token"].ToString());
if (keyint == sqlkey)
{
dbCon.Close();
delQuery(keyint);
}
}

Related

Having trouble with: The connection was not closed. The connection's current state is open. - SQL Server & C#

I'm currently working on the login form of a school management system. The thing is that when I try to log in, I get an error:
System.InvalidOperationException: The connection was not closed. The connection's current state is open
It says that the error is on the 30th line of code but I can't seem to find a way to solve it.
Here's the code of the method in which the error occurs:
public void LoginTeacher()
{
try
{
command = new SqlCommand("TeacherLogin", connection);
command.CommandType = CommandType.StoredProcedure;
connection.Open(); // This is the 30th line.
command.Parameters.AddWithValue("#username", Txt_User.Text);
command.Parameters.AddWithValue("#password", Txt_Pass.Text);
SqlDataReader dataReader = command.ExecuteReader();
if (dataReader.Read())
{
TeacherDash teacherDash = new TeacherDash();
this.Hide();
teacherDash.lblusertype.Text = dataReader[1] + " " + dataReader[2].ToString();
teacherDash.ShowDialog();
this.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
connection.Close();
}
}
Immediately after that error is shown there is another one that says:
System.InvalidOperationException: Invalid attempt to call CheckDataIsReady when reader is closed
and points to line 71 which is the following:
public void Login()
{
try
{
command = new SqlCommand("SP_USER_LOGIN", connection);
command.CommandType = CommandType.StoredProcedure;
connection.Open();
command.Parameters.AddWithValue("#user", Txt_User.Text);
command.Parameters.AddWithValue("#pass", Txt_Pass.Text);
SqlDataReader dataReader = command.ExecuteReader();
if (dataReader.Read())
{
LoginTeacher();
if (dataReader[10].Equals("Admin"))
{
AdminDash adminDash = new AdminDash();
this.Hide();
adminDash.lblusertype.Text = dataReader[1] + " " + dataReader[2].ToString();
adminDash.ShowDialog();
this.Close();
}
There's more code after that but I don't find it relevant since it's the same thing but with the different type of users.
Thanks in advance!
You could try changing your TeacherLogin() method to something like the following:
public void TeacherLogin()
{
try
{
using(SqlConnection con = new SqlConnection("connection string"))
{
using(SqlCommand cmd = new SqlCommand("TeacherLogin"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#username", Txt_User.Text);
cmd.Parameters.AddWithValue("#password", Txt_Pass.Text);
cmd.Connection = con;
con.Open();
using(SqlDataReader dr = cmd.ExecuteReader())
{
while(dr.Read())
{
TeacherDash teacherDash = new TeacherDash();
this.Hide();
teacherDash.lblusertype.Text = string.Format("{0} {1}", dr[1], dr[2]);
teacherDash.ShowDialog();
}
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
There's no need to use finally{} to close the connection as its all wrapped in a using() block, it will close and dispose on its own when the code leaves the block. I'd always recommend using SQL connections and commands in this way as not doing so can cause issues by leaving connections open.
Database object need to be closed and disposed. Keeping them local to the method where they are used lets you make sure this happens. using blocks take care of this for you.
I used a DataTable instead of testing with the reader because the connection must remain open as long as the reader is in use. Opening and closing the connection in the briefest possible time is important.
Please don't use .AddWithValue. See http://www.dbdelta.com/addwithvalue-is-evil/
and
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
and another one:
https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications
Here is another
https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html
Of course you will have to check your database for the real datatypes and field size to have the correct .Add method.
public void LoginTeacher()
{
DataTable dt = new DataTable();
using (SqlConnection cn = new SqlConnection("your connection string"))
using (SqlCommand cmd = new SqlCommand("TeacherLogin", cn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#username",SqlDbType.VarChar,100 ).Value = Txt_User.Text;
cmd.Parameters.Add("#password",SqlDbType.VarChar, 100 ).Value =Txt_Pass.Text;
cn.Open();
dt.Load(cmd.ExecuteReader());
} //Your connection and command are both disposed
if (dt.Rows.Count > 0)
{
TeacherDash teacherDash = new TeacherDash();
teacherDash.lblusertype.Text = $"{dt.Rows[0][1]} {dt.Rows[0][2]}";
teacherDash.ShowDialog();
Close();
}
else
MessageBox.Show("Sorry, login failed");
}

Id request from the database

I'm trying to write the id request from the database. This is how I wrote it:
public int QueryId(String query)
{
var temp = this.connection;
MySqlCommand verifica = new MySqlCommand(query, connection);
var queryResult = verifica.ExecuteScalar();
return Convert.ToInt32(verifica.ExecuteScalar());
}
This is how I make use of the function:
MySqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
nomeCorrente = reader.GetString("nome");
cognomeCorrente = reader.GetString("cognome");
idCorrente = db.QueryId("SELECT id FROM thewishlist.user WHERE email='" + user.Text + "'");
}
reader.Close();
db.CloseConnection();
It does not generate errors, but when I run the project and log out the user gives me the following error:
MySql.Data.MySqlClient.MySqlException There is already an open DataReader associated with this Connection which must be closed first.
The error is pretty clear. I suggest you make use of using statement and also since you're only returning one column you and use ExcecuteScalar instead of ExecuteReader. So your code will look something like:
var id = 0;
var query = "SELECT ID FROM thewishlist.user WHERE email = #email";
using (var con = new SqlConnection(this.connection))
{
using (var cmd = new SqlCommand(query, con))
{
con.Open();
cmd.Parameters.Add("#email", SqlDbType.NVarChar).Value = user.Text;
id = (int)cmd.ExecuteScalar();
}
}//connection will auto close here and object will get disposed
return id;
Also to prevent sql injection you should always use paramertised sql queries.
As Jason said that, you should close the reader firstly, then call db.QueryId to execute the new query, I modifed your code as follows:
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
nomeCorrente = reader.GetString("nome");
cognomeCorrente = reader.GetString("cognome");
}
reader.Close();
}
idCorrente = db.QueryId("SELECT id FROM thewishlist.user WHERE email='" + user.Text + "'");
db.CloseConnection();

Equivalent C# of this PHP code

Trying to learn C# and I can't quite get a handle on querying and getting results. I'm trying to figure out both how to and the best way of doing the below in C# .NET. It's a MySql database.
//Interact with the DB. Find out if this hashed account #'s in there.
$dbh = $this->getPDO();
$procedure = "SELECT userPass FROM 499Users WHERE accName = :acc";
$call = $dbh->prepare($procedure);
$call->bindParam(':acc', $testAcc, PDO::PARAM_STR);
$call->execute();
//Fetch up to 1 result row
$row = $call->fetch(PDO::FETCH_ASSOC);
This is my latest try: Also I realize I should probably be using parameters, but I just want it to work first
MySqlConnectionStringBuilder conn_string = new MySqlConnectionStringBuilder();
conn_string.Server = "*";
conn_string.UserID = "*";
conn_string.Password = "*";
conn_string.Database = "*";
conn_string.Port = 3306;
MySqlConnection connection = new MySqlConnection(conn_string.ToString());
try
{
Console.WriteLine("Trying to connect to: ..." + conn_string); Console.WriteLine("Connecting to MySQL...");
connection.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
string hashedAcc = this.HashPassword(acc);
//Verify hashed account
string query = "SELECT userPass FROM 49Users WHERE accName =" + hashedAcc;
MySqlCommand cmd = new MySqlCommand(query, connection);
MySqlDataReader myReader;
myReader = cmd.ExecuteReader();
try
{
while (myReader.Read())
{
Console.WriteLine(myReader.GetString(0));
}
}
finally
{
myReader.Close();
connection.Close();
}
The following WHERE clause:
WHERE accName =" + hashedAcc;
will cause an error if accName is not of type int, it needs quotes around it.
You should use parameterized query just like you did in PDO, it avoid errors like this and SQL injections as well.
var query = "SELECT userPass FROM 49Users WHERE accName = #hashedAcc";
var cmd = new MySqlCommand(query, connection);
cmd.Parameters.AddWithValue("#hashedAcc", hashedAcc);

How can I get SQL result into a STRING variable?

I'm trying to get the SQL result in a C# string variable or string array. Is it possible? Do I need to use SqlDataReader in some way?
I'm very new to C# functions and all, used to work in PHP, so please give a working example if you can (If relevant I can already connect and access the database, insert and select.. I just don't know how to store the result in a string variable).
This isn't the single greatest example in history, as if you don't return any rows from the database you'll end up with an exception, but if you want to use a stored procedure from the database, rather than running a SELECT statement straight from your code, then this will allow you to return a string:
public string StringFromDatabase()
{
SqlConnection connection = null;
try
{
var dataSet = new DataSet();
connection = new SqlConnection("Your Connection String Goes Here");
connection.Open();
var command = new SqlCommand("Your Stored Procedure Name Goes Here", connection)
{
CommandType = CommandType.StoredProcedure
};
var dataAdapter = new SqlDataAdapter { SelectCommand = command };
dataAdapter.Fill(dataSet);
return dataSet.Tables[0].Rows[0]["Item"].ToString();
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
finally
{
if (connection != null)
{
connection.Close();
}
}
}
It can definitely be improved, but it would give you a starting point to work from if you want to go down a stored procedure route.
Try This:
SqlConnection con=new SqlConnection("/*connection string*/");
SqlCommand SelectCommand = new SqlCommand("SELECT email FROM table1", con);
SqlDataReader myreader;
con.Open();
myreader = SelectCommand.ExecuteReader();
List<String> lstEmails=new List<String>();
while (myreader.Read())
{
lstEmails.Add(myreader[0].ToString());
//strValue=myreader["email"].ToString();
//strValue=myreader.GetString(0);
}
con.Close();
accessing the Emails from list
lstEmails[0]->first email
lstEmails[1]->second email
...etc.,
You could use an SQL Data Reader:
string sql = "SELECT email FROM Table WHERE Field = #Parameter";
string variable;
using (var connection = new SqlConnection("Your Connection String"))
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("#Parameter", someValue);
connection.Open();
using (var reader = command.ExecuteReader())
{
//Check the reader has data:
if (reader.Read())
{
variable = reader.GetString(reader.GetOrdinal("Column"));
}
// If you need to use all rows returned use a loop:
while (reader.Read())
{
// Do something
}
}
}
Or you could use SqlCommand.ExecuteScalar()
string sql = "SELECT email FROM Table WHERE Field = #Parameter";
string variable;
using (var connection = new SqlConnection("Your Connection String"))
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("#Parameter", someValue);
connection.Open();
variable = (string)command.ExecuteScalar();
}
This May help you For MySQL
MySqlDataReader reader = mycommand.ExecuteReader();
while (reader.Read())
{
TextBox2.Text = reader.ToString();
}
For SQL
using (SqlCommand command = new SqlCommand("*SELECT QUERY HERE*", connection))
{
//
// Invoke ExecuteReader method.
//
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
TextBox2.Text = reader.GetString(0);
}
}
Try this:
public string SaveStringSQL(string pQuery, string ConnectionString)
{
var connection = new Conexao(ConnectionString);
connection.Open();
SqlCommand command = new SqlCommand(pQuery, connection.Connection);
var SavedString = (string)command.ExecuteScalar();
connection.Close();
return SavedString;
}
The ExecuteScalar function saves whatever type of data there is on your database - you just have to specify it.
Keep in mind that it can only save one line at a time.

Sending several SQL commands in a single transaction

I have a huge list of INSERT INTO ... strings. Currently I run them with:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
foreach (var commandString in sqlCommandList)
{
SqlCommand command = new SqlCommand(commandString, connection);
command.ExecuteNonQuery();
}
}
I see that each ExecuteNonQuery() also executes commit.
Is there a way to insert all rows in a single transaction (commit in the end)?
The reason I want a single transaction is to make my "inserts" process faster. Will a single transaction also make it quicker?
Its recommended to use SQL transaction in case you are executing Multiple queries in one thread , you can have it like this :
SqlTransaction trans;
try
{
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
trans = connection.BeginTransaction();
foreach (var commandString in sqlCommandList)
{
SqlCommand command = new SqlCommand(commandString, connection,trans);
command.ExecuteNonQuery();
}
trans.Commit();
}
catch (Exception ex) //error occurred
{
trans.Rollback();
//Handel error
}
You might probably gain some performance by using just one single transaction and command, as follows:
using (SqlConnection connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
using (SqlTransaction trans = connection.BeginTransaction())
{
using (SqlCommand command = new SqlCommand("", connection,trans))
{
command.CommandType = System.Data.CommandType.Text;
foreach (var commandString in sqlCommandList)
{
command.CommandText = commandString;
command.ExecuteNonQuery();
}
}
trans.Commit();
}
}
catch (Exception ex) //error occurred
{
//Handel error
}
}
A little late, but if you are inserting all of the values into the same table, code the SQL insert as "insert into tablex (f1, f2, f3,...) values (#F1,#F2,#F3...)". Create the command and add the parameters #F1..., and then set the Prepare flag on the command. Now as you loop through your list of values to insert, you can set them into the appropriate parameters and then do the ExecuteNonQuery. SQL will pre-parse the command string once, and then use the new parameters each time. This is a bit faster.
Finally, you can execute multiple SQL statements in a single command by appending ';' to each statement, if you must execute the entire string. You can bunch a number of these commands together and make one request to SQL server to execute them.
You can just concatenate the sql and let the server handle it:
using (SqlConnection connection = new SqlConnection(connectionString))
{
string lsSql = string.Empty;
foreach (var commandString in sqlCommandList)
{
lsSql = lsSql + commandString + " ; " + Environment.NewLine;
}
connection.Open();
SqlCommand command = new SqlCommand(lsSql, connection);
command.ExecuteNonQuery();
}
Here is what I use on my daily work, before it a use a foreach for any non-query that I need to run on database. You can see that I'm using the OracleCommand, but if you need you can change to SQL statement
public static void ExecuteDatabaseNonQuery(string command)
{
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
OracleTransaction transaction;
transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
cmd.Transaction = transaction;
try
{
cmd.CommandText = command;
var update = cmd.ExecuteNonQuery();
transaction.Commit();
Console.WriteLine("{0} rows updated", update);
}
catch (Exception e)
{
transaction.Rollback();
throw new Exception("Error: " + e);
}
}
Note: If theres any uncommited changes on database this method will wait indefinitely
You can use Parallel for each
using (SqlConnection connection = new SqlConnection(connectionString))
{
List<string> sqlCommandList = new List<string>();
connection.Open();
Parallel.ForEach(sqlCommandList, commandString =>
{
SqlCommand command = new SqlCommand(commandString, connection);
command.ExecuteNonQuery();
});
}

Categories