Invalid attempt to call read when reader is closed when inserting data - c#

i have a button that when clicked inserts data from textbox and combobox fields into database tables, but every time i insert it gives me "Invalid attempt to call read when reader is closed". How can i get rid of this error. And tips on optimising the code are welcome, because i know im a total noob. thanks
private void btnSave_Click(object sender, RoutedEventArgs e)
{
try
{
SqlConnection sqlCon = new SqlConnection(#"Data Source=(localdb)\mssqllocaldb; Initial Catalog=Storagedb;");
sqlCon.Open();
string Query1 = "insert into location(Storage, Shelf, columns, rows) values(" + txtWarehouse.Text + ", " + txtShelf.Text + ", " + txtColumn.Text + ", " + txtRow.Text + ")";
SqlCommand sqlCmd = new SqlCommand(Query1, sqlCon);
SqlDataAdapter dataAdp = new SqlDataAdapter(sqlCmd);
dataAdp.SelectCommand.ExecuteNonQuery();
sqlCon.Close();
}
catch (Exception er)
{
MessageBox.Show(er.Message);
}
try
{
SqlConnection sqlCon = new SqlConnection(#"Data Source=(localdb)\mssqllocaldb; Initial Catalog=Storagedb;");
sqlCon.Open();
string Query3 = "SELECT LOCATION_ID FROM LOCATION WHERE storage='" + txtWarehouse.Text + "' AND shelf='" + txtShelf.Text + "' AND columns='"
+ txtColumn.Text + "' AND rows='" + txtRow.Text + "'";
SqlCommand sqlCmd1 = new SqlCommand(Query3, sqlCon);
SqlDataReader dr = sqlCmd1.ExecuteReader(); ;
while (dr.Read())
{
string LocationId = dr[0].ToString();
dr.Close();
string Query2 = "insert into product(SKU, nimetus, minimum, maximum, quantity,location_ID,category_ID,OrderMail_ID) values ('" + txtSku.Text + "','" + txtNimetus.Text + "', '"
+ txtMin.Text + "', '" + txtMax.Text + "', '" + txtQuan.Text + "', '" + LocationId + "', '" + (cbCat.SelectedIndex+1) + "', '" + (cbMail.SelectedIndex+1) + "')";
SqlCommand sqlCmd = new SqlCommand(Query2, sqlCon);
SqlDataAdapter dataAdp = new SqlDataAdapter(sqlCmd);
dataAdp.SelectCommand.ExecuteNonQuery();
}
sqlCon.Close();
}
catch (Exception ed)
{
MessageBox.Show(ed.Message);
}
}

Let's try to make some adjustments to your code.
First thing to consider is to use a parameterized query and not a
string concatenation when you build an sql command. This is mandatory
to avoid parsing errors and Sql Injections
Second, you should encapsulate the disposable objects in a using statement
to be sure they receive the proper disposal when you have finished to
use them.
Third, you can get the LOCATION_ID from your table without running a
separate query simply adding SELECT SCOPE_IDENTITY() as second batch to your first command. (This works only if you have declared the LOCATION_ID field in the first table as an IDENTITY column)
Fourth, you put everything in a transaction to avoid problems in case
some of the code fails unexpectedly
So:
SqlTransaction tr = null;
try
{
string cmdText = #"insert into location(Storage, Shelf, columns, rows)
values(#storage,#shelf,#columns,#rows);
select scope_identity()";
using(SqlConnection sqlCon = new SqlConnection(.....))
using(SqlCommand cmd = new SqlCommand(cmdText, sqlCon))
{
sqlCon.Open();
using( tr = sqlCon.BeginTransaction())
{
// Prepare all the parameters required by the command
cmd.Parameters.Add("#storage", SqlDbType.Int).Value = Convert.ToInt32(txtWarehouse.Text);
cmd.Parameters.Add("#shelf", SqlDbType.Int).Value = Convert.ToInt32(txtShelf.Text);
cmd.Parameters.Add("#columns", SqlDbType.Int).Value = Convert.ToInt32(txtColumn.Text );
cmd.Parameters.Add("#rows", SqlDbType.Int).Value = Convert.ToInt32(txtRow.Text);
// Execute the command and get back the result of SCOPE_IDENTITY
int newLocation = Convert.ToInt32(cmd.ExecuteScalar());
// Set the second command text
cmdText = #"insert into product(SKU, nimetus, minimum, maximum, quantity,location_ID,category_ID,OrderMail_ID)
values (#sku, #nimetus,#min,#max,#qty,#locid,#catid,#ordid)";
// Build a new command with the second text
using(SqlCommand cmd1 = new SqlCommand(cmdText, sqlCon))
{
// Inform the new command we are inside a transaction
cmd1.Transaction = tr;
// Add all the required parameters for the second command
cmd1.Parameters.Add("#sku", SqlDbType.NVarChar).Value = txtSku.Text;
cmd1.Parameters.Add("#nimetus",SqlDbType.NVarChar).Value = txtNimetus.Text;
cmd1.Parameters.Add("#locid", SqlDbType.Int).Value = newLocation;
.... and so on for the other parameters required
cmd1.ExecuteNonQuery();
// If we reach this point the everything is allright and
// we can commit the two inserts together
tr.Commit();
}
}
}
}
catch (Exception er)
{
// In case of exceptions do not insert anything...
if(tr != null)
tr.Rollback();
MessageBox.Show(er.Message);
}
Notice that in the first command I use parameters of type SqlDbType.Int because you haven't used single quotes around your text. This should be verified against the real data type of your table columns and adjusted to match the type. This is true as well for the second command where you put everything as text albeit some of those fields seems to be integer (_location_id_ is probably an integer). Please verify against your table.

Related

Check SQL for Book_Availability before issuing one (BookAvailability-1)

If I put "if, foreach, and else statement under comment //", the program works and Reduces book count by 1 from SQL database. But I want to check IF there is at least 1 available book to give. This code keeps showing me the message in "else" statement if I leave it like this. Help is needed fast, it's my final project, that is needed to be done before 23.07. :(
int book_qty = 0;
SqlCommand cmd2 = connection.CreateCommand();
cmd2.CommandType = CommandType.Text;
cmd2.CommandText = "SELECT * FROM Book_list WHERE BookName = '" + TextBoxBookName + "'";
cmd2.ExecuteNonQuery();
DataTable dt2 = new DataTable();
SqlDataAdapter da2 = new SqlDataAdapter(cmd2);
da2.Fill(dt2);
foreach (DataRow dr2 in dt2.Rows)
{
book_qty = Convert.ToInt32(dr2["book_qty"].ToString());
}
if (book_qty > 0)
{
SqlCommand cmd = connection.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO Issue_book VALUES(" + TextBoxSearchMembers.Text + ",'" + TextBoxMemberName.Text + "','" + TextBoxMemberContact.Text + "','" + TextBoxMemberEmail.Text + "','" + TextBoxBookName.Text + "', '" + DateTimePicker1.Text + "')";
cmd.ExecuteNonQuery();
SqlCommand cmd1 = connection.CreateCommand();
cmd1.CommandType = CommandType.Text;
cmd1.CommandText = "UPDATE Book_list SET BookAvailability = BookAvailability-1 WHERE BookName ='" + TextBoxBookName.Text + "'";
cmd1.ExecuteNonQuery();
MessageBox.Show("successful issue");
this.Close();
else
{
MessageBox.Show("Book not available");
}
You are only checking book_qty from the last row in your result set instead of BookAvailability for all rows. You probably want to do something like:
SqlCommand cmd2 = connection.CreateCommand();
cmd2.CommandType = CommandType.Text;
cmd2.CommandText = "SELECT BookAvailability FROM Book_list WHERE BookName = '" + TextBoxBookName + "'";
var result = cmd2.ExecuteScalar();
book_qty = Convert.ToInt32(result);
You need to make sure that there is only one book with the given bookname available.
In that case just correcting this one line in your code would help as well:
book_qty = Convert.ToInt32(dr2["book_qty"].ToString());
to
book_qty = Convert.ToInt32(dr2["BookAvailability"].ToString());
Otherwise you'd need to query SUM(BookAvailability), but the following code would decrease the amount of books for multiple books at once, that wouldn't be good.
Untested code. I don't have your database. Comments and explanation in line.
private void OPCode()
{
try
{
//keep your connections close to the vest (local)
using (SqlConnection connection = new SqlConnection())
//a using block ensures that your objects are closed and disposed
//even if there is an error
{
using (SqlCommand cmd2 = new SqlCommand("SELECT BookAvailability FROM Book_list WHERE BookName = #BookName", connection))
{
//Always use parameters to protect from sql injection
//Also it is easier than fooling with the single quotes etc.
//If you are referring to a TextBox you need to provide what property is
//being accessed. I am not in a WPF right now and not sure if .Text
//is correct; may be .Content
//You need to check your database for correct data type and field size
cmd2.Parameters.Add("#BookName", SqlDbType.VarChar, 100).Value = TextBoxBookName.Text;
//A select statement is not a non-query
//You don't appear to be using the data table or data adapter
//so dump them extra objects just slow things dowm
connection.Open();
//Comment out the next 2 lines and replaced with
//Edit Update
//var returnVal = cmd2.ExecuteScalar() ?? 0;
//if ((int)returnVal > 0)
//*************************************************************
//Edit Update
//*************************************************************
//in case the query returns a null, normally an integer cannot
//hold the value of null so we use nullable types
// the (int?) casts the result of the query to Nullable of int
Nullable<int> returnVal = (int?)cmd2.ExecuteScalar();
//now we can use the .GetValueOrDefault to return the value
//if it is not null of the default value of the int (Which is 0)
int bookCount = returnVal.GetValueOrDefault();
//at this point bookCount should be a real int - no cast necessary
if (bookCount > 0)
//**************************************************************
//End Edit Update
//**************************************************************
{
using (SqlCommand cmd = new SqlCommand("INSERT INTO issue_book VALUES(#SearchMembers etc", connection))
{
//set up the parameters for this command just like the sample above
cmd.Parameters.Add("#SearchMembers", SqlDbType.VarChar, 100).Value = TextBoxSearchMembers.Text;
cmd.ExecuteNonQuery();
}
using (SqlCommand cmd1 = new SqlCommand("UPDATE Book_list SET BookAvailability = BookAvailability-1 WHERE BookName = #BoxBookName;", connection))
{
cmd1.Parameters.Add("#BoxBookName", SqlDbType.VarChar, 100);
cmd1.ExecuteNonQuery();
}
MessageBox.Show("success");
this.Close();
}
else
{
MessageBox.Show("Book not available");
}
}
}
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString());
}
}

On adding data, it show "Too few parameters. Expected 1"

The problem says:
Too few parameters. Expected 1.
Here's my database table:
CustomerOrder [CustomerOrder(OrderId, ProdName, ProdPrice, OrderQty, CatName, OrderDate]
Code:
con.Open();
OleDbCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CustomerOrder(OrderId, ProdName, ProdPrice, OrderQty, CatName, OrderDate)values('" + txtOrderCode.Text + "','" + txtProdName.Text + "', '" + txtProdPrice.Text + "', '" + txtOrderQty.Text + "', '" + txtCatName.Text + "', '" + txtOrderDate.Text + "')";
cmd.ExecuteNonQuery();
tabControl1.SelectedTab = tabPage1;
DataTable dt = new DataTable();
OleDbDataAdapter da = new OleDbDataAdapter(cmd);
da.Fill(dt);
dataGridView1.DataSource = dt;
con.Close();
int ordercode, orderqty;
double price;
string prodname, catname;
ordercode = Convert.ToInt32(txtOrderCode.Text);
orderqty = Convert.ToInt32(txtOrderQty.Text);
price = Convert.ToDouble(txtProdPrice.Text);
prodname = Convert.ToString(txtProdName.Text);
I would recommend you use parametrised queries instead as it will prevent you from Sql Injection Attacks. Here is a small example of how it could work with your code
String SqlCommand = "insert into CUSTOMERORDER values (#OrderId, #ProdName,#ProdPrice,#OrderQty, #CatName,#OrderDate)";
SqlCommand cmd = new SqlCommand(SqlCommand , //ConnectionString);
cmd.CommandType = CommandType.Text;
conn.Open();
cmd.Parameters.AddWithValue("#OrderId", txtOrderCode.Text);
cmd.Parameters.AddWithValue("#ProdName", txtProdName.Text );
cmd.Parameters.AddWithValue("#ProdPrice", txtProdPrice.Text);
cmd.Parameters.AddWithValue("#OrderQty", txtOrderQty.Text);
cmd.Parameters.AddWithValue("#CatName", txtCatName.Text.Text);
cmd.Parameters.AddWithValue("#OrderDate", txtOrderDate.Text);
cmd.ExecuteNonQuery();
conn.Close();
Read up on Sql Injection attacks. Doing it this way is much easier, cleaner and most importantly, safer. Also when looking at your code you are setting the text box values after you have ran the Sql command

eliminating duplicate records insertion into database

The below is my code to insert gridview data into a database. However, using this I want to check and restrict insertion into the database where records have the same name, location, education and salary. If all of these are the same and those already present in database they should not get inserted. If any one column is different then they should get inserted.
protected void btn_insert_Click(object sender, EventArgs e)
{
foreach (GridViewRow g1 in GridView1.Rows)
{
SqlConnection con = new SqlConnection(connStr);
cmd = new SqlCommand("insert command", con);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
UploadStatusLabel.Text = "Records Inserted Successfully";
}
I think hitting the database inside a for loop is a very bad idea when you have other options. I'm not tackling this issue in the below sample.
Your code may be subject to SQL Injection, you need to use parameters to pass your values. If someone filled the input with ";DROP TABLE OpenOfficetext;" and they have DROP permissions, it will be a problem if you're just concatenating strings.
To avoid duplicates, you can check first if a similar record exists.
foreach (GridViewRow g1 in GridView1.Rows)
{
string insertCommand = "insert into OpenOfficetext(Name, Location, Education, Salary) values(#p1, #p2, #p3, #p4)";
string selectCommand = "SELECT COUNT(*) FROM OpenOfficetext WHERE Name = #p1 AND Location = #p2 AND Education = #p3 AND Salary = #p4";
SqlConnection con = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand(selectCommand, con);
con.Open();
cmd.Parameters.AddWithValue("#p1", g1.Cells[0].Text);
cmd.Parameters.AddWithValue("#p2", g1.Cells[1].Text);
cmd.Parameters.AddWithValue("#p3", g1.Cells[2].Text);
cmd.Parameters.AddWithValue("#p4", g1.Cells[3].Text);
if (Convert.ToInt32(cmd.ExecuteScalar()) == 0)
{
cmd.CommandText = insertCommand;
cmd.ExecuteNonQuery();
}
con.Close();
}
please use the below code
if not exist (select * from OpenOfficetext where Name='" + g1.Cells[0].Text + "' and Location='" + g1.Cells[1].Text + "' and Education = '" + g1.Cells[2].Text + "' and Salary = '" + g1.Cells[3].Text + "' )
Begin
SqlConnection con = new SqlConnection(connStr);
cmd = new SqlCommand("insert into OpenOfficetext(Name,Location,Education,Salary) values ('" + g1.Cells[0].Text + "','" + g1.Cells[1].Text + "','" + g1.Cells[2].Text + "','" + g1.Cells[3].Text + "')", con);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
End

DataReader exception in C#

Let me first explain my code.
fetch fare for selected destination(reader1)
fetch * for selected user id(reader2)
insert data to process new balance(cmd3)
retrieve new balance as calculated field (reader3)
update travel account (cmd5)
clear calc_tb for next customer input(cmd6)
My problem is while running, I get this error:
There is already an open DataReader associated with this Command which must be closed first.
How can I handle more than one DataReader accessing different tables?
Is DataReader similar to resultset in Java ?
I'm a beginner in coding.
string id,
destin, num;
id = txt_id.Text;
destin = cb_destin.Text;
num = cb_num.Text;
string sql1 = "SELECT fare FROM route_info WHERE route_name='" +destin + "' ";
string sql2 = "SELECT * FROM trav_acc WHERE user_id='" + id + "'";
con.Open();
cmd1 = new SqlCommand(sql1, con);
reader1 = cmd1.ExecuteReader();
while(reader1.Read())
{
string fare = (string)reader1["fare"];
cmd2 = new SqlCommand(sql2, con);
reader2 = cmd2.ExecuteReader();
string cur_bal = (string)reader2["cur_bal"];
reader2.Close();
string calc1 = "INSERT INTO calc_tb VALUES('" + cur_bal + "','" + num + "','" + fare + "')";
string calc2 = "SELECT cur_bal - (nof_tickets * fare) AS new_bal FROM calc_tb";
cmd3 = new SqlCommand(calc1, con);
cmd3.ExecuteNonQuery();
cmd4 = new SqlCommand(calc2, con);
reader3 = cmd4.ExecuteReader();
while(reader3.Read())
{
string new_bal = (string)reader3["new_bal"];
string update = "UPDATE trav_acc SET cur_bal='" + new_bal + "',last_bal='" + cur_bal + "' WHERE user_id='" + id + "' ";
cmd5 = new SqlCommand(update, con);
cmd5.ExecuteNonQuery();
string clear = "DELETE FROM calc_tb";
cmd6 = new SqlCommand(clear, con);
cmd6.ExecuteNonQuery();
}
}
con.Close();
MessageBox.Show("Thank you for using EasyTravel.Come again soon!");
}
It seems that reader3 and cmd4 are defined outside of the code you are showing us, but at least they are defined outside the loop for reader1. So if your reader1 contains more than one row, reader3 and cmd4 will be assigned again, but the "old" reader3 is never closed. Close reader3 when it's finished reading. Or use a using statement, which will take care of the closing automatically.
using (DataReader reader1 = cmd1.ExecuteReader()) {
....
while (reader1.Read()) {
....
using (DataReader reader3 = cmd4.ExecuteReader()) {
while (reader3.Read()) {
}
} //reader3 is closed here automatically
}
} //reader1 is closed here automatically
Furthermore, I'm not sure if I remember correctly, but I think it's not possible to have two open readers on the same connection. I may be wrong with this, though.

SQLite query not executing on C#

The code doesn't produce an exception and seemingly runs fine. But after running it and checking the table i see that the row is not inserted. I use navicat for cheking and that's not the problem. I've double-checked the table name and field names which all correct. What's wrong?
try
{
SQLiteConnection conn = new SQLiteConnection("Data Source=path;Version=3;FailIfMissing=True");
conn.Open();
String sql = "INSERT INTO sales (cust_id, date, cost, price) VALUES ('0', '" + String.Format("{0:u}", DateTime.Now.Date).Split(' ')[0] + "', '" + cost.ToString() + "', '12')";
SQLiteCommand command = new SQLiteCommand(sql, conn);
conn.Close();
}
catch (Exception err)
{
MessageBox.Show(err.Message.ToString());
}
add:
command.ExecuteNonQuery();
on your code:
...
SQLiteCommand command = new SQLiteCommand(sql, conn);
command.ExecuteNonQuery();
Also it is always better to use parameters on your query

Categories