Это широко распространенный шаблон ADO.NET для извлечения данных из базы данных с помощью устройства чтения данных, но, как ни странно, он не работает.
Не работает:
public static IEnumerable<IDataRecord> SelectDataRecord<T>(string query, string connString)
where T : IDbConnection, new()
{
using (var conn = new T())
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.Connection.ConnectionString = connString;
cmd.Connection.Open();
using (var reader = (DbDataReader)cmd.ExecuteReader())
{
// the main part
while (reader.Read())
{
yield return (IDataRecord)reader;
}
}
}
}
Это работает:
public static IEnumerable<IDataRecord> SelectDataRecord<T>(string query, string connString)
where T : IDbConnection, new()
{
using (var conn = new T())
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.Connection.ConnectionString = connString;
cmd.Connection.Open();
using (var reader = (DbDataReader)cmd.ExecuteReader())
{
// the main part
foreach (var item in reader.Cast<IDataRecord>())
{
yield return item;
}
}
}
}
Единственное соответствующее изменение, которое я вижу, это то, что в первом коде итератор возвращается из цикла while
, а во втором - из цикла foreach
.
Я называю это следующим образом:
// I have to buffer for some reason
var result = SelectDataRecord<SQLiteConnection>(query, connString).ToList();
foreach(var item in result)
{
item.GetValue(0); // explosion
}
Я пробовал с SQLite коннектор .NET, а также MySQL. Результат тот же, то есть первый подход сбой, второй успешный.
Exception
SQLite
Необработанное исключение типа "Исправление System.InvalidOperationException" произошло в System.Data.SQLite.dll. Дополнительная информация: Нет текущей строки
MySQL
Необработанное исключение типа "System.Exception" произошло в MySql.Data.dll. Дополнительная информация: Нет текущего запроса в считывателе данных
Это из-за различий в реализации между reader.Read
и reader.GetEnumerator
в конкретных соединителях ADO.NET? Я не видел заметной разницы, когда я проверил источник проекта System.Data.SQLite
, GetEnumerator
вызывает Read
внутренне. В идеале я предполагаю, что в обоих случаях ключевое слово yield
предотвращает нетерпеливое выполнение метода, и циклы должны выполняться только после того, как перечисление перечислили извне.
Обновление:
Я использую этот шаблон как безопасный (по существу такой же, как и второй подход, но немного менее подробный),
using (var reader = cmd.ExecuteReader())
foreach (IDataRecord record in reader as IEnumerable)
yield return record;