如何将IDataReader最佳方法用作IEnumerable<T>?

5

我需要在任何IDataReader实现中使用Linq,就像这样:

var c = sqlDataReader.AsEnumerable().Count();

例子:

public abstract class Test
{
    public abstract SqlDataReader GetSqlDataReader();

    public void Foo()
    {
        SqlDataReader sqlDataReader = GetSqlDataReader();
        IEnumerable<SqlDataReader> sqlEnumerable = sqlDataReader.AsEnumerable();
        var c = sqlEnumerable.Count();
        var s = sqlEnumerable.Sum();
        SqlDataReader first = sqlEnumerable.First();
        var t = first.GetSqlXml(10);
    }
}

最佳编写方法是什么?请编写您的片段。

7个回答

19

您可以使用这个:

MyDataReader.Cast<IDataRecord>()

但不要忘记在关闭DataReader之前执行linq语句。例如,使用ToList()。


8
您可以创建一个扩展方法来实现这个功能(请注意以下注意事项)

public static class DataReaderExtension
{
    public static IEnumerable<Object[]> AsEnumerable(this System.Data.IDataReader source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        while (source.Read())
        {
            Object[] row = new Object[source.FieldCount];
            source.GetValues(row);
            yield return row;
        }
    }
}

在这里找到:http://www.thinqlinq.com/default/Consuming-a-DataReader-with-LINQ.aspx


正如@LukeH所指出的那样,需要注意的是由于IDataReader仅支持向前读取一次,因此您只能对可枚举对象进行一次查询。(要解决这个问题,您可以调用ToList/ToArray,然后查询它)。

请注意,SqlDataReader已经实现了IEnumerable,因此您不需要在您给出的示例中执行此操作。

另外,请注意最好通过服务器(例如通过LINQ to SQL)执行任何过滤/聚合操作。


请注意,这样做不允许他们像示例中那样先进行“计数”,然后进行“求和”,最后进行“第一个”操作。(尽管为了实现这一点,他们需要重新查询数据库三次或以某种方式缓冲结果,但这两种方法都存在潜在的陷阱。) - LukeH
请注意,SqlDataReader已经实现了IEnumerable接口,因此您不需要在给出的示例中这样做。是的,这是正确的,但我需要IEnumerable <SqlDataReader> 用于例如“GetSqlXml”方法。 - b2Lord
"IEnumerable<Object[]> AsEnumerable",我需要IEnumerable<SqlDataReader>。 - b2Lord
"你只能查询可枚举对象一次",我知道,但是我可以使用"AsQueryable()"来解决这个问题。 - b2Lord
@b2Lord,IEnumerable<SqlDataReader> 没有意义,因为你只有一个 SqlDataReader,你不是在枚举多个 SqlDataReader - George Duckett
显示剩余3条评论

8

试试这个:

public static class DataReaderExtension
{
    public class EnumeratorWrapper<T>
    {
        private readonly Func<bool> moveNext;
        private readonly Func<T> current;

        public EnumeratorWrapper(Func<bool> moveNext, Func<T> current)
        {
            this.moveNext = moveNext;
            this.current = current;
        }

        public EnumeratorWrapper<T> GetEnumerator()
        {
            return this;
        }

        public bool MoveNext()
        {
            return moveNext();
        }

        public T Current
        {
            get { return current(); }
        }
    }

    private static IEnumerable<T> BuildEnumerable<T>(
            Func<bool> moveNext, Func<T> current)
    {
        var po = new EnumeratorWrapper<T>(moveNext, current);
        foreach (var s in po)
            yield return s;
    }

    public static IEnumerable<T> AsEnumerable<T>(this T source) where T : IDataReader
    {
        return BuildEnumerable(source.Read, () => source);
    }
}

7

这是我的个人看法:

public static IEnumerable<T> Enumerate<T>(this T reader) where T: IDataReader 
{ 
   using(reader) 
      while(reader.Read()) 
         yield return reader; 
} 

public void Test()
{
  var Res =
    from Dr in MyDataReader.Enumerate()
    select new {
      ID = (Guid)Dr["ID"],
      Description = Dr["Desc"] as string
    };
}

我觉得有必要发布这篇文章,因为在使用完DataReader后进行处理非常重要,但是没有一个答案提到这一点。

这就是为什么我的实现在while循环周围有一个using语句。通过这种方式,我可以进行“单手”查询而不必担心DataReader的处理。


1
你的代码看起来没问题,但是对于更复杂的表格可能会出现错误,导致迭代停止而无法释放读取器。我曾经遇到过这个问题,所以我创建了一个“DisposableEnumerable”类,使得使用在枚举方法之外完成,并在出现问题时调用Dispose。 - xxxo
对于关于using语句的建议以及关闭数据读取器的重要性,给予+1。在某些环境中,DataReader实际上可能会锁定数据库、表、叶子等。 - GoldBishop

6
您可以将DataReader加载到DataTable中,然后使用Select()函数:
DataTable dt = new DataTable();
dt.Load(dataReader);
DataRow[] rows = dt.Select();        //DataRow[] Implements IEnumerable

或者

IEnumerable<DataRow> rows = dt.AsEnumerable();

嗨,Gabriel。看起来你有两个账户(http://stackoverflow.com/users/5547620/gabrielk,http://stackoverflow.com/users/5547612/gabriel-kniznik)。如果你想要将它们关联起来,请按照 http://stackoverflow.com/help/merging-accounts 上的步骤进行操作。 - Matt

0
今天我实现了以下通用解决方案,因为我们不喜欢依赖于任何第三方依赖项或任何复杂的解决方案。 使用反射+lambda表达式进行简单快速的IDataReaderIEnumerable<T>转换
public IEnumerable<T> ConvertToEnumerable<T>(SqlDataReader dr) where T : class, new()
{
    List<string> lstColumns = Enumerable.Range(0, dr.FieldCount).Select(dr.GetName).ToList();
    List<PropertyInfo> lstProperties = typeof(T).GetProperties().Where(x => lstColumns.Contains(x.Name, StringComparer.OrdinalIgnoreCase)).ToList();
    while (dr.Read())
    {
        var entity = new T();
        lstProperties.Where(w => dr[w.Name] != System.DBNull.Value).ToList().ForEach(i => i.SetValue(entity, dr[i.Name], null));
        yield return entity;
    }
}

用法

SqlDataReader dr;
...
var result = ConvertToEnumerable<Foo>(dr).FirstOrDefault();
Or
var result = ConvertToEnumerable<Foo>(dr).ToList();

在每个记录中运行Where()检查并进行属性和ToList()分配似乎不是很快。 - Joel Coehoorn

0

我曾经使用过以下方法,但我更喜欢@Serge的建议,它让我想起了我曾经做过但后来忘记了IDataReader实现的内容。

var reader = command.ExecuteReader();
var records = Enumerable
    .Range(0, int.MaxValue)
    .TakeWhile(i => reader.Read())
    .Select(i => reader as IDataRecord);

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