5

I need to use Linq on any IDataReader implementations like this

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

Example:

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

What is the best way to write this. Please, write your snippet.

b2Lord
  • 69
  • 1
  • 1
  • 3

7 Answers7

19

You can use this:

MyDataReader.Cast<IDataRecord>()

But don't forget to have the linq statement execute before you close the DataReader.
using ToList() for instance

Serge
  • 6,554
  • 5
  • 30
  • 56
8

You could create an extension method to do this (see caveats below):

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

Found here: http://www.thinqlinq.com/default/Consuming-a-DataReader-with-LINQ.aspx


As pointed out by @LukeH, note that as IDataReader only supports reading once, forwards, you'll only be able to query the enumerable once. (To get round this you could call ToList/ToArray, then query that).

Note that SqlDataReader already impliments IEnumerable so you won't need to do this in the example you've given.

Also, be aware that it's probably better to do any filtering/aggrigating on the server (via LINQ to SQL for example)

George Duckett
  • 31,770
  • 9
  • 95
  • 162
  • Note that this won't allow them to do `Count` followed by `Sum` followd by `First` as in the example. (Although to do that they'd either need to re-query the db three times, or buffer the results somehow, both of which have potential pitfalls.) – LukeH Aug 04 '11 at 09:38
  • @George Duckett "Note that SqlDataReader already impliments IEnumerable so you won't need to do this in the example you've given.", yes it's true but I need IEnumerable to use for example method "GetSqlXml" – b2Lord Aug 04 '11 at 09:54
  • "IEnumerable AsEnumerable", I need IEnumerable – b2Lord Aug 04 '11 at 09:56
  • "you'll only be able to query the enumerable once" I know, but I can use "AsQueryable()" to do this. – b2Lord Aug 04 '11 at 09:57
  • @b2Lord, `IEnumerable` doesn't make sense, as you only have one `SqlDataReader`, you're not enumerating over more than one `SqlDataReader`. – George Duckett Aug 04 '11 at 10:00
  • while (reader.Read()) { // In this loop it is possible to do somthing with "reader", so I also want to so somthing in any enumerabled item } – b2Lord Aug 04 '11 at 10:07
  • @b2Lord, but the enumerated item isn't the `SqlDataReader`, the `SqlDataReader` is the thing doing the enumerating. I'm sorry, i still don't follow your use-case. – George Duckett Aug 04 '11 at 10:12
  • I'm looking at the definitions for both [`SqlDataReader`](https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader(v=vs.110).aspx) and [`IDataReader`](https://msdn.microsoft.com/en-us/library/system.data.idatareader(v=vs.110).aspx), neither of them actually implement `IEnumerable`...? –  Oct 24 '15 at 07:03
8

Try, this:

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);
    }
}
halorty
  • 644
  • 1
  • 7
  • 11
7

Here are my two cents :

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

I felt the urge to post this, because it is very important to dispose a DataReader after use, and no answer mentioned it.

That's why my implementation has a using statement around the while loop. In this way, I can do "one hand" queries whithout worrying about the DataReader disposal.

Larry
  • 17,605
  • 9
  • 77
  • 106
  • 1
    It seems ok for your code but with more complex tables there could be an error which would stop the iteration and never dispose the reader. I had this issue and I created a "DisposableEnumerable" class so the using is done outside the Enumerate method and calls Dispose in case of problem. – xxxo May 13 '17 at 06:58
  • +1 for making the suggestions about the `using` statement and the importance of closing the datareader. In some environments, the DataReader could actually `Lock` a database, table, leaf, etc. – GoldBishop Oct 09 '17 at 13:26
6

You can just load the DataReader into a DataTable and then Select():

DataTable dt = new DataTable();
dt.Load(dataReader);
DataRow[] rows = dt.Select();        //DataRow[] Implements IEnumerable

or

IEnumerable<DataRow> rows = dt.AsEnumerable();
Matt
  • 74,352
  • 26
  • 153
  • 180
  • Hi Gabriel. It seems you have two accounts (http://stackoverflow.com/users/5547620/gabrielk, http://stackoverflow.com/users/5547612/gabriel-kniznik). If you wish to link them, please follow the steps at http://stackoverflow.com/help/merging-accounts – Matt Nov 19 '15 at 21:27
0

I've used the following but I like @Serge's suggestion better and it reminds be of what I believe I used to do but then forgot that IDataReader implements

var reader = command.ExecuteReader();
var records = Enumerable
    .Range(0, int.MaxValue)
    .TakeWhile(i => reader.Read())
    .Select(i => reader as IDataRecord);
jpierson
  • 16,435
  • 14
  • 105
  • 149
0

Today I implemented the following generic solution as we don't like to depend on any third party dependencies or any complex solution.

Simple and Fast IDataReader to IEnumerable<T> conversion using reflection + lambda expressions

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

Usages

SqlDataReader dr;
...
var result = ConvertToEnumerable<Foo>(dr).FirstOrDefault();
Or
var result = ConvertToEnumerable<Foo>(dr).ToList();
halfer
  • 19,824
  • 17
  • 99
  • 186
Vaibhav J
  • 1,316
  • 8
  • 15