Еще раз, акт составления моих мыслей на вопрос раскрывает ответ. В частности, последнее предложение, где я написал «по одной строке за раз». Я понял, что мне действительно все равно, что это устройство чтения данных, если я могу перечислять его построчно. Это привело меня к этому:
public IEnumerable<IDataRecord> GetSomeData(string filter)
{
string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter";
using (SqlConnection cn = new SqlConnection(GetConnectionString()))
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter;
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return (IDataRecord)rdr;
}
}
}
}
Это будет работать еще лучше, когда мы перейдем к 3.5 и сможем начать использовать другие операторы linq для результатов, и мне это нравится, потому что это заставляет нас задуматься в терминах "конвейера" между каждым уровнем для запросов, которые возвращают много результатов.
Недостатком является то, что читателям, имеющим более одного набора результатов, будет неудобно, но это крайне редко.
Обновление
С тех пор как я впервые начал играть с этим шаблоном в 2009 году, я понял, что было бы лучше, если бы я также сделал его универсальным типом возврата IEnumerable<T>
и добавил параметр Func<IDataRecord, T>
для преобразования состояния DataReader в бизнес-объекты цикла. В противном случае могут возникнуть проблемы с отложенной итерацией, так что вы каждый раз видите последний объект в запросе.