使用SqlDataReader的foreach循环?

14

我有以下代码:

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();
}

哪种方法行得通。

显然,我可以使用常规的for循环实现相同的结果,但是我认为foreach语法更清晰,因此在可能的情况下使用它。

这里出了什么问题?在C#中,foreach循环不像其他高级语言那样灵活吗?


1
我不确定,但我认为foreach正在迭代记录集中的每个字段而不是每条记录... - Chris
3个回答

25

类似以下这种方式。请注意,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();

更新:回应Caleb Bell的评论。

我同意Enumerate更好一些。

事实上,在我的个人“通用”库中,我已经将上述内容替换为IDataReader的扩展方法:

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

而调用者可以使用以下方式获得强类型对象:

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

那个 GetFromReader<T> 方法非常漂亮和优雅,真遗憾这样的东西不是原生地在框架中!我创建了一个扩展方法版本并将其命名为 Enumerate<T>。 - Caleb Bell
@CalebBell - 我同意Enumerate是更好的名称,详见更新。 - Joe

17
< p > 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 循环实际上是不可比较的。


我明白了。那有点烦人。 - Oliver
1
@Joe 在 SqlDataReader 上,它似乎返回整个记录。 - Adam Houldsworth
尽管我的答案与其他答案基本相同,但出于自己的利益,我会保留它。我从未尝试过将foreach与reader一起使用,我总是进入while(reader.Read())代码块,称其为手指的肌肉记忆。话虽如此,最终结果基本相同,因此我不觉得有必要重新访问旧代码并进行重构以满足强迫症 :-) - Adam Houldsworth
@MarcGravell - 有没有特别的原因,为什么通常使用 while(reader.Read()) 而不是 foreach(它们似乎都可以工作,那么为什么偏爱其中一个)? - BornToCode
1
首先,这仅适用于抽象基类(DbDataReader),而不适用于接口(IDataReader)。其次,它涉及迭代器的额外分配。第三,很难确定枚举对象是什么。第四,它允许更少的行/网格粒度以及您何时可以/无法访问元数据。第五,它实际上并没有帮助您,因为您最终访问数据记录的方式与读取器相同。基本上,Read()运行良好。如果您喜欢foreach:也可以。 - Marc Gravell
显示剩余3条评论

2

你也可以这样做...

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"]);
          }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接