0

I need to process the data from the list using SqlDataReader. To do this, I wrote a for loop in which the data for the query will be supplied and get into SqlDataReader. But after the first iteration, the loop breaks. An error is displayed that you need to close SqlDataReader.

List<string> days = new List<string>() { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };

SqlConnection cnn = new SqlConnection(@"Con");
cnn.Open();

SqlCommand cmd = cnn.CreateCommand();
List<string> data = new List<string>();

for (int i = 0; i < days.Count;i++)
{
    cmd.CommandText = $@"select * from Table where Day = '{days[i]}'";
    
    SqlDataReader reader = cmd.ExecuteReader();
    
    while (reader.Read())
    {
        data.Add(reader[0].ToString());
    }
}

Here, for example, I use the "days" list, but in the program itself the list is obtained from a query, so it can be larger. This is where the error occurs. I tried to close SqlDataReader, but the problem is that it cannot be opened back. In any case, I need to somehow get data from SqlDataReader in a loop.

5
  • 1
    Always use parameterized sql and avoid string concatenation to add values to sql statements. This mitigates SQL Injection vulnerabilities and ensures values are passed to the statement correctly. See How can I add user-supplied input to an SQL statement?, Exploits of a Mom, and Why do we always prefer using parameters in SQL statements?. Commented Apr 2, 2023 at 13:31
  • Even when the parameter values are known it is a good habit to use parameters instead. Commented Apr 2, 2023 at 13:32
  • Implement a class whose properties map to the table columns and then try opening and closing your connection inside the loop Commented Apr 2, 2023 at 13:35
  • 3
    The main idea - query in a loop - is suspicious. SQL query is expencive: we should pass query via net (time), RDBM must parse it (time), optimize (time), validate credential, execute (and may be read from HDD), return results... If yo want to rob a bank, do it once and take a million, and stop keep doing it whenever you need a tenner for a glass of beer. Here we can easly change loop into a single in Commented Apr 2, 2023 at 13:41
  • @DmitryBychenko I like that metaphor. Commented Apr 2, 2023 at 16:08

1 Answer 1

2

Yes, you should Dispose instances that implement IDisposable: a quick patch, but not the best soultion is to add using:

...

for (int i = 0; i < days.Count; i++) {
  cmd.CommandText = $@"select * from Table where Day = '{days[i]}'";
    
  // using to Dispose reader
  using (SqlDataReader reader = cmd.ExecuteReader()) {
    while (reader.Read())
      data.Add(reader[0].ToString());
  }
}

A better aproach is to call query just once (SQL is expensive):

var data = new List<string>();

var days = new List<string>() { 
  "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" 
};

using SqlConnection cnn = new SqlConnection(@"Con");

cnn.Open();

//TODO: change * into the only field(s) you want to fetch
// Since days are list of constants I haven't created bind variables:
//   - No sql injection
//   - Constants are specific and in specific order (workdays) so
//     I want the query be specific and not mixed with in (:param1, ... :paramN)
//     for arbitrary days 
string sql = 
  $@"select *
       from Table
      where day in ({string.Join(", ", days.Select(day => $"'{day}'"))})";

using SqlCommand cmd = cnn.CreateCommand(sql, cnn);

using SqlDataReader reader = cmd.ExecuteReader();

while (reader.Read()) {
  data.Add(Convert.ToString(reader[0]));
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.