Вам нужно открытое и активное соединение при переборе DataReader. Если вы закроете его перед возвратом устройства чтения данных, оно не будет работать. Что я обычно делаю, чтобы обойти это, это превращаю DataReader в IEnumerable, вот так:
public IEnumerable<IDataRecord> GetProductsFromDB()
{
string strConnection = ConfigurationManager.ConnectionStrings["dbconn"].ConnectionString;
using (SqlConnection connection = new SqlConnection(strConnection))
using (SqlCommand cmd = new SqlCommand("GetProducts", connection))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
using (var sdr = cmd.ExecuteReader())
{
while (sdr.Read())
{
yield return sdr;
}
}
}
}
Обратите внимание, что я также немного изменил порядок: подождите как можно дольше, чтобы открыть соединение, и поместите соединение и создание команды рядом с каждым, чтобы избежать такого большого количества вложений.
Этот шаблон открывает совершенно новый мир для того, как вы пишете свой код доступа к данным, потому что теперь внезапно ваши необработанные запросы sql и вызовы хранимых процедур работают с операторами linq-to-objects. Вы можете сделать немного круто, как это:
foreach (var product in GetProductsFromDB()
.Select(i => CreateProductFromDataRow(i))
.Where(p => p.IsOnSale()) )
{
// do something with products that are on sale
}