asp.net datareader must be closed - c#

My code:
SqlConnection con = new SqlConnection(WebConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString);
SqlCommand cmd = new SqlCommand();
//..........
cmd.CommandText = "SELECT * FROM TempQn WHERE creatorId= '" +
Session["administratorID"].ToString() + "'";
dr = cmd.ExecuteReader();
while (dr.Read())
{
int ids = Int32.Parse(dr["QuestionID"].ToString());
cmd.CommandText = " INSERT INTO Answers (QuestionId,Answer) Select c.QnId, c.Answer From TempAns c Where c.Id = " + ids + " ";
cmd.ExecuteNonQuery(); //this line
}
dr.Close();
The error is:
There is already an open DataReader associated with this Command which must be closed first.
What kind of command should replace the cmd.ExecuteNonQuery();?

You can't execute any further SQL statements as long as the DataReader is "active".
To overcome this, store list of the SQL statements then exeucute them after reading:
cmd.CommandText = "SELECT * FROM Question WHERE SurveyID= '" + sID + "'";
dr = cmd.ExecuteReader();
List<string> arrSQL = new List<string>();
while (dr.Read())
{
int ids = Int32.Parse(dr["QuestionID"].ToString());
arrSQL.Add("INSERT INTO Answers (QuestionId,Answer) Select c.QnId, c.Answer From TempAns c Where c.Id = " + ids + " ");
}
dr.Close();
arrSQL.ForEach(strSQL =>
{
cmd.CommandText = strSQL;
cmd.ExecuteNonQuery();
});
Your current code is vulnerable to SQL injection attacks though and isn't good practice - you better use Parameter instead of injecting value to the raw SQL - here is how to achieve that:
cmd.CommandText = "SELECT * FROM Question WHERE SurveyID=#id";
cmd.Parameters.AddWithValue("#id", sID);
dr = cmd.ExecuteReader();
List<int> arrQuestions = new List<int>();
while (dr.Read())
{
int ids = Int32.Parse(dr["QuestionID"].ToString());
arrQuestions.Add(ids);
}
dr.Close();
cmd.CommandText = "INSERT INTO Answers (QuestionId, Answer) Select c.QnId, c.Answer From TempAns c Where c.Id = #id";
arrQuestions.ForEach(id =>
{
cmd.Parameters["#id"].Value = id;
cmd.ExecuteNonQuery();
});

You already have one command associated with "cmd".
dr = cmd.ExecuteReader();
while (dr.Read())
{
int ids = Int32.Parse(dr["QuestionID"].ToString());
SqlCommand sqlCmd = new SqlCommand("INSERT INTO Answers (QuestionId,Answer) Select c.QnId, c.Answer From TempAns c Where c.Id = " + ids + " ");
sqlCmd.ExecuteNonQuery(); //this line
}
dr.Close();
So Like ive given above create a new command for the insertion.

This single query should do the job (not sure of you exact data model, adapt if required ):
INSERT INTO Answers (QuestionId,Answer)
Select c.QnId, c.Answer
From TempAns c
inner join Question q on c.QnId = q.Id
where q.SurveyID = #SurveyID
In order to avoid SQl Injection, use this C# code :
cmd.CommandTest = #"INSERT INTO Answers (QuestionId,Answer)
Select c.QnId, c.Answer
From TempAns c
inner join Question q on c.QnId = q.Id
where q.SurveyID = #SurveyID";
SqlParameter param = cmd.Parameters.Add("#SurveyID", SqlDbType.Int);
param.Value = yourSurveyId;
cmd.Open(); // it would be better to check the status before
cmd.ExecuteNonQuery();
cmd.Close();

Instead of using a 2nd connection object, you could change your connection string and use MARS (Multiple active result set) for this purpose. Add the following statement to your connection string:
MultipleActiveResultSets=True
EDIT:
And like the other's said, use SqlParameters for your parameters and not string concatenation. It's not only a security issue, but also a huge performance hit!

you need to declare a new command object because cmd is already being used for reading the data when you are trying to use it for insert statement. Also, don't use string concatenation from sql command, its a bad practice and vulnerable to SQL injection. using parameters.

Related

database INNER JOIN

These are the two set up tables
LOGIN TABLE
USER'S NAME
I want to create something like the User will key in their USER_ID and USER_PWD in a textbox. IF the user successfully login, it will say " HI + PATNAME ".
I have created this code so far but it isnt working.
string sqlStr = "Select patpro.'PATNAME' FROM patpro,useform where USER_ID=#name and USER_PWD=#password and useform.'USER_ID' = patpro.'USERID'";
cmd.Parameters.AddWithValue("#name", txtValue.Text);
cmd.Parameters.AddWithValue("#password", txtPassword.Password);
cmd.CommandText = sqlStr;
cmd.Connection = connection;
connection.Open();
MySqlDataReader login = cmd.ExecuteReader();
if (login.HasRows)
{
login.Read();
string name = (login["USER_ID"].ToString());
txtAssignID1.Text = "Login verified. Hi, " + name + "\n";
}
From what I see, you're trying to use login["USER_ID"].ToString() which USER_ID is a nonexistent column definition inside current SELECT statement. Hence, you should add column names which defined in SELECT results like login["PATNAME"] and use proper INNER JOIN statement instead:
string sqlStr = #"SELECT patpro.PATNAME FROM patpro INNER JOIN useform
ON useform.USER_ID = patpro.USERID
WHERE useform.USER_ID = #name AND useform.USER_PWD = #password";
cmd.Parameters.AddWithValue("#name", txtValue.Text);
cmd.Parameters.AddWithValue("#password", txtPassword.Password);
cmd.CommandText = sqlStr;
cmd.Connection = connection;
connection.Open();
MySqlDataReader login = cmd.ExecuteReader();
if (login.HasRows)
{
// read value inside the loop, because MySqlDataReader is forward-only
while (login.Read())
{
string name = login["PATNAME"].ToString();
txtAssignID1.Text = "Login verified. Hi, " + name + "\n";
}
}
Additional note: Better to use using statement for MySqlConnection, MySqlCommand and MySqlDataReader to ensure immediate disposal of MySQL connection objects after fetching query results.

I Want to get id from select query in C# but every time i run the program, The query returns me "-1" [duplicate]

This question already has answers here:
Return value of a select statement
(7 answers)
Closed 5 years ago.
I am trying To get ID against selected name in Drop Down list by using select query but it always returns the value "-1" instead of relevant result.
SqlCommand cmd2 = con.CreateCommand();
cmd2.CommandType = CommandType.Text;
cmd2.CommandText = "Select Pid From Provinces where Pname = '" + pr + "'";
cmd2.CommandText = "Select Pid From Provinces where Pname = '" + prov.Text + "'";
int pid = cmd2.ExecuteNonQuery();
You need to use ExecuteScalar instead of ExecuteNonQuery
int pid = Convert.ToInt32(cmd2.ExecuteScalar());
For more details please refer Link
The reason is that ExecuteNonQuery doesn't return the database value when using a Select command - It returns a return code for success or failure.
If you want to read the database value, use the following code. Note that I used an SqlParameter instead of your parameter concatenation, which can cause SQL injections and is a poor practice:
SqlCommand cmd2 = con.CreateCommand();
cmd2.CommandType = CommandType.Text;
cmd2.CommandText = "Select Pid From Provinces where Pname=#pr";
cmd2.Parameters.Add(new SqlParameter("pr", pr));
int result = Convert.ToInt32(cmd2.ExecuteScalar());
Alternativly, you can use fill a DataTable with multiple results:
SqlCommand cmd2 = con.CreateCommand();
cmd2.CommandType = CommandType.Text;
cmd2.CommandText = "Select Pid From Provinces where Pname=#pr";
cmd2.Parameters.Add(new SqlParameter("pr", pr));
SqlConnection Connection = new SqlConnection(ConnectionString);
SqlDataAdapter adp = new SqlDataAdapter(cmd2);
// Create a new datatable which will hold the query results:
DataTable dt = new DataTable();
Connection.Open();
// Fill a datatable with the query results:
adp.Fill(dt);
Connection.Close();
Let me add few notes for you before answer the question, You should aware about the usage of ExecuteNonQuery, and why other peoples refer ExecuteScalar for you. here is the difference you have to note.
ExecuteNonQuery() does not return data at all: only the number of rows affected by an insert, update, or delete
ExecuteScalar() only returns the value from the first column of the first row of your query.
There is few more things I want to remind you, As a developer we won't give the key to hackers through SqlInjection, for that we should use parameterization like the following:
using(SqlCommand cmdSql = con.CreateCommand())
{
cmdSql.CommandType = CommandType.Text;
cmdSql.CommandText = "Select Pid From Provinces where Pname =#Pname";
cmdSql.Parameters.Add("#Pname ", SqlDbType.VarChar).Value= prov.Text;
int pid = Convert.ToInt32(cmdSql.ExecuteScalar());
}

Doesn't work th SqlCommand with multiple queries

With one SELECT query, the code seems to add to the listbox correctly, but when I add another query, the listbox doesn't show anything anymore, and it seems that that rdr[3] does not exists (Contact has 3 columns and Numar_contact has one column (should't it be this one the rdr[3]?))
string connString = #"database=Agenda_db; Data Source=Marian-PC\SQLEXPRESS; Persist Security Info=false; Integrated Security=SSPI";
SqlConnection conn = new SqlConnection(connString);
try {
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM Contact;"+ "SELECT * FROM Numar_contact", conn)
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
listBox1.Items.Add(rdr[0].ToString() + ' ' + rdr[1].ToString() + ' ' + rdr[2].ToString()+' '+ rdr[3].ToString());
}
rdr.Close();
Join your queries with a UNION. The way you've got it now, it'll return two results sets.
SELECT [col1], [col2] FROM Contact
UNION ALL
SELECT [col1], [col2] FROM Numar_contact
As DJ KRAZE pointed out in a comment, it might not be a bad idea to wrap this in a sproc or a TVF. But this will work too.
Edit:
I just learned via comments that the two tables are actually unrelated. In light of that, I'd be tempted to use two SqlCommands with two, distinct foreach loops. But if you're sold on this way,
SELECT id_contact, nume_contact, prenume_contact FROM Contact
UNION ALL
SELECT id_contact, numar, NULL FROM Numar_contact
This will align the data from the two tables, but where the second table doesn't have a [prenume_contact] it will select NULL. I might have mixed up the column positions here, since I don't really understand what those names are meant to represent.
Edit 2:
string connString = #"database=Agenda_db; Data Source=Marian-PC\SQLEXPRESS; Persist Security Info=false; Integrated Security=SSPI";
using (SqlConnection conn = new SqlConnection(connString))
{
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Contact", conn))
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
listBox1.Items.Add(rdr[0].ToString() + " " + rdr[1].ToString() + " " + rdr[2].ToString());
}
}
using (SqlCommand cmd2 = new SqlCommand("SELECT * FROM Numar_contact", conn))
using (SqlDataReader rdr2 = cmd.ExecuteReader())
{
while (rdr2.Read())
{
listBox1.Items.Add(rdr2[0].ToString() + " " + rdr2[1].ToString());
}
}
}
catch { }
}
Edit 3, thanks to insight from Scott Chamberlain:
On the other hand, you might want to perform a JOIN of some kind, most commonly an INNER JOIN. Note that this is an entirely different operation from any we've talked about before.
SELECT Contact.id_contact, Contact.nume_contact, Contact.prenume_contact, Numar_contact.numar
FROM Contact
INNER JOIN Numar_contact on Contact.id_contact = Numar_contact.id_contact
This will tie the two tables together, returning a record for each contact-numar_contact. Again, this is definitely not the same as doing a UNION. Make sure you're aware of the difference before you pick which you want.
Use this if your second table contains data that relates many-to-one to the first table.
Thanks to your comment, what you are wanting to do is JOIN the tables.
SELECT Contact.id_contact, nume_contact, prenume_contact, numar
FROM Contact
INNER JOIN Numar_contact on Contact.id_contact = Numar_contact.id_contact
That will combine the two tables in to four columns where id_contact matches in both tables.
You may want a INNER JOIN or a LEFT JOIN depending on if you want rows to show up only when there is a item in the 2nd table or show up anyway and just make the 4th column DBNull.Value.
Yes you can.
Here is an example from the MSDN I've modified to use your code - you need to move the reader to the Next ResultSet
string connString = #"database=Agenda_db; Data Source=Marian-PC\SQLEXPRESS; Persist Security Info=false; Integrated Security=SSPI";
SqlConnection conn = new SqlConnection(connString);
SqlCommand myCommand = new SqlCommand("SELECT * FROM Contact; SELECT * FROM Numar_contact", conn);
SqlDataReader myReader ;
int RecordCount=0;
try
{
myConnection.Open();
myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
//Write logic to process data for the first result.
RecordCount = RecordCount + 1;
}
MessageBox.Show("Total number of Contacts: " + RecordCount.ToString());
bool moreResults = myReader.NextResult(); // <<<<<<<<<<< MOVE TO NEXT RESULTSET
RecordCount = 0;
while (moreResults && myReader.Read())
{
//Write logic to process data for the second result.
RecordCount = RecordCount + 1;
}
MessageBox.Show("Total number from Numar_contacts: " + RecordCount.ToString());
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
conn.Close(); // Could be replaced with using statement too
}

How do I solve syntax error in from clause?

string query = "select a.Name,a.Add from GroupDetails a join General b on a.ID=b.Id where b.AccCode='" + label1.text + "'";
OleDbCommand cmd = new OleDbCommand(query, con);
OleDbDataAdapter daName = new OleDbDataAdapter(cmd);
OleDbDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
txtName.Text = dr["a.Name"].ToString();
txtAdd.Text = dr["a.Add"].ToString();
}
There shows an exception
Syntax error in FROM clause
If it explicitly mentions from, then my guess would be that the SQL backend doesn't support aliases (the a). However, there are multiple problems:
the alias
the sql concatenation
the unnecessary adapter
the incorrect columns being read
I would suggest trying:
const string query = "select GroupDetails.Name,GroupDetails.Add from GroupDetails join General on GroupDetails.ID=General.Id where General.AccCode=#accCode";
using(var cmd = new OleDbCommand(query, con))
{
cmd.Parameters.AddWithValue("accCode", label1.text);
using(var dr = cmd.ExecuteReader())
{
txtName.Text = (string)dr["Name"];
txtAdd.Text = (string)dr["Add"];
}
}
I'm sure that your query is not SQL Server because it works OK in SQL Server query window. It could be Access SQL or My SQL, and you have to specify explicitly left join, right join or inner join. I think you want inner join in this case:
string query = "SELECT a.Name,a.Add FROM GroupDetails a INNER JOIN General b ON a.ID=b.Id WHERE b.AccCode='" + label1.text + "'";
Add is the Keyword of SQL, you can [] the field like a.[Add]
string query = "select a.Name,a.[Add] from GroupDetails a join General b on a.ID=b.Id where b.AccCode='" + label1.text + "'";

SqlDataReader Execution error

i m trying to retrieve the Specialization ID from a table called Specializationtbl, using C# MSVS 2008 and the table includes SpecializationName and SpecializationID beside some other rows and my question is related to some error " No Data to present ", the command goes as bellow:
SqlCommand READSpecID = new SqlCommand("SELECT * FROM Specializationtbl WHERE SpecializationName='" + comboBox1.Text + "'" , DBcnction);
DBcnction.Open();
SqlDataReader ReadSpecID_ = READSpecID.ExecuteReader();
ReadSpecID_.Read();
int SpecID_ = Convert.ToInt16(ReadSpecID_["SpecID"].ToString());
DBcnction.Close();
i also tried to Select the "SpecID" instead of all the rows, but cant seem to seal the query correctly and keep receiving "No data present " error, any idea where am i making the mistake?
1) Try opening DBcnction before assigning the value to READSPecID
DBcnction.Open();
SqlCommand READSpecID = new SqlCommand("SELECT * FROM Specializationtbl WHERE SpecializationName='" + comboBox1.Text + "'" , DBcnction);
2) Run the command in SSMS:
SELECT * FROM Specializationtbl WHERE SpecializationName ='yourvalue'
and see if any results are returned
3) Check comboBox1.Text has a value in it
4) Validate the contents of comboBox1.Text (Or use paremetrised queries or a stored procedure) to ensure you do not become a victim of SQL Injection: http://en.wikipedia.org/wiki/SQL_injection
Refactor to solve your TWO problems:
Your SQL injection problem when building your SQL statement.
Use ExecuteScalar if you only need one value.
Implement using blocks.
string retVal;
using (var conn = new SqlConnection(SomeConnectionString))
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT SpecID FROM Specializationtbl WHERE SpecializationName= #Name";
cmd.Parameters.AddWithValue("#Name", comboBox1.Text);
conn.Open();
retVal = cmd.ExecuteScalar().ToString();
}
int specID = int.Parse(retVal);
If you really needed more than one value from your statement:
using (var conn = new SqlConnection(SomeConnectionString))
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT SpecID, Value2 FROM Specializationtbl WHERE SpecializationName= #Name";
cmd.Parameters.AddWithValue("#Name", comboBox1.Text);
conn.Open();
var dr = cmd.ExecuteReader();
while (dr.Read())
{
Customer c = new Customer {
ID = dr["SpecID"].ToString(),
Value = dr["Value2"].ToString(),
};
}
}
Need to first test if there are any rows. I suspect the query is returning zero rows.
if (ReadSpecID_.HasRows)
{
ReadSpecID_.Read();
}

Categories

Resources