Nimrod Yanai Nimrod Yanai - 7 months ago 39
SQL Question

An open datareader associated with this command error in C#

I want to build a simple loop to check incoming data from SQL server, compare it to a textfield, and execute non query if there are no duplicates.

I wrote this code:

try
{
bool exists = false;
conn = new SqlConnection(DBConnectionString);

SqlCommand check_user = new SqlCommand("SELECT usrEmail FROM tblUsers", conn);
SqlCommand add_user = new SqlCommand("INSERT INTO tblUsers (usrEmail, usrPassword, usrRealname, usrIsowner) VALUES (@email, @pass, @name, @owner)", conn);
// (I have removed all the paramaters from this code as they are working and irrelevant)
conn.Open();

SqlDataReader check = check_user.ExecuteReader();

while (check.Read())
{
if (Convert.ToString(check[0]) == UserEmail.Text)
{
MessageBox.Show("The email you entered already exists in the system.");
exists = true;
break;
}
}

if (exists == false)
{
add_user.ExecuteNonQuery();
}
else
{
return;
}
}
catch (Exception ex)
{
MessageBox.Show("There was a problem uploading data to the database. Please review the seller's details and try again. " + ex.Message);
return;
}
finally
{
conn.Close();
}


I used breakpoints and saw that the code runs the while loop fine, but when it reaches the ExecuteNonQuery command, it returns an error message:


there is already an open datareader associated with this command which
must be closed first


I tried to use a
check.Close();
command, but when I do, it suddenly gets stuck with the duplicate email error message for reasons passing understanding.
Additionally, there was a fix I tried in which the data actually WAS sent to the database (I saw it in SQL Server Management Studio), but still gave an error message... That was even stranger, since the nonquery command is the LAST in this function. If it worked, why did it go to the catch?

I have searched the site for answers, but the most common answers are MARS (I have no idea what that is) or a dataset, which I do not want to use in this case.

Is there a simple solution here? Did I miss something in the code?

Answer

The simples way out would be:

using(SqlDataReader check = check_user.ExecuteReader())
{
    while (check.Read())
    {
        if (Convert.ToString(check[0]) == UserEmail.Text)
        {
            MessageBox.Show("The email you entered already exists in the system.");
            exists = true;
            break;
        }
    }
}

That said, there are some serious problems with this code.

First of all, you don't really want to read all users just to check that an email address is already taken. select count(*) from tblUsers where usrEmail = @email is fine...

...or not, because there's a possibility of a race condition. What you should do is add a unique constraint on a usrEmail column and just insert into tblUsers, catching violations. Or you can use merge if you feel like it.

Next, you don't really want to have your data access code all over the place. Factor it out into separate classes/methods at least.