Foreach с SqlDataReader?

У меня есть следующий код:

SqlDataReader reader = getAddressQuery.sqlReader;
while (reader.Read())
{
    foreach (Object ob in reader)
    {
        someText.InnerText = someText.InnerText + " " + ob.ToString();
    }
}

Код в цикле foreach не выполняется. Однако я могу это сделать:

SqlDataReader reader = getAddressQuery.sqlReader;
while (reader.Read())
{
    someText.InnerText = reader[0].ToString();
}

Что работает.

Очевидно, я мог бы достичь того же результата, используя регулярный цикл, а не цикл foreach, но я думаю, что синтаксис foreach более ясен, поэтому я использую его, когда это возможно.

Что здесь случилось? Являются ли циклы foreach в С# не такими гибкими, как на языках более высокого уровня?

Ответ 1

Что-то вроде следующего. Обратите внимание, что IDataReader происходит от IDataRecord, который предоставляет членам, используемым для обработки текущей строки:

IEnumerable<IDataRecord> GetFromReader(IDataReader reader)
{
    while(reader.Read()) yield return reader;
}

foreach(IDataRecord record in GetFromReader(reader))
{
    ... process it ...
}

Или даже что-то вроде следующего, чтобы получить перечисление или список строго типизированных объектов объекта от читателя:

IEnumerable<T> GetFromReader<T>(IDataReader reader, Func<IDataRecord, T> processRecord)
{
    while(reader.Read()) yield return processRecord(reader);
}

MyType GetMyTypeFromRecord(IDataRecord record)
{
    MyType myType = new MyType();
    myType.SomeProperty = record[0];
    ...
    return myType;
}

IList<MyType> myResult = GetFromReader(reader, GetMyTypeFromRecord).ToList();

UPDATE в ответ на комментарий Caleb Bell.

Я согласен Enumerate - лучшее имя.

Фактически в моей личной "общей" библиотеке я теперь заменил выше метод расширения на IDataReader:

public static IEnumerable<IDataRecord> Enumerate(this IDataReader reader)
{
    while (reader.Read())
    {
        yield return reader;
    }
}

И вызывающий может получить строго типизированные объекты, используя:

reader.Enumerate.Select(r => GetMyTypeFromRecord(r))

Ответ 2

foreach предоставляет IDataRecord, который помещает вас в очень похожую лодку к циклу while:

using (SqlConnection conn = new SqlConnection(""))
using (SqlCommand comm = new SqlCommand("select * from somewhere", conn))
{
    conn.Open();

    using (var r = comm.ExecuteReader())
    {
        foreach (DbDataRecord s in r)
        {
            string val = s.GetString(0);
        }
    }
}

Если вы хотите увидеть что-то более полезное, вам нужно будет иметь свой собственный код, который извлекает значения из записи в нечто более обычное, как предложил другой ответ.

В любом случае вам понадобится специальный код, независимо от того, имеет ли он его встроенный или нет или использует цикл while или нет, зависит от того, как часто он будет написан, я полагаю, не более одного раза, и вы, вероятно, должны вставлять его в где-то вспомогательный метод.

И чтобы ответить на несколько вопросов: проблема не в foreach, это ваше попытка использования того, что она возвращает для вас, поскольку сопоставимое использование цикла while на самом деле не сравнимо.

Ответ 3

вы также можете сделать это...

string sql = "select * from Users";
using (SqlConnection conn = GetConnection()){ 

    conn.Open();
    using (SqlDataReader rdr = new SqlCommand(sql, conn).ExecuteReader()){

           foreach (DbDataRecord c in rdr.Cast<DbDataRecord>()){
               Console.Write("{0} {1} ({2}) - ", (string)c["Name"], (string)c["Surname"], (string)c["Mail"]);
               Console.WriteLine((string)c["LoginID"]);
          }
    }
}