tags:

views:

746

answers:

3

There doesn't seem to be a Peek method on the DataReader in ado.net. I would like to be able to perform some one-off processing before I loop through my reader, and it would be nice to be able to look at the data in the first row without causing it to be skipped by the subsequent iteration. What is the best way to accomplish this?

I am using a SqlDataReader, but preferably the implementation would be as general as possible (i.e. apply to IDataReader or DbDataReader).

+3  A: 

You don't need a Peek() method. You can accomplish what you need with a Do While loop.

So instead of

while(dr.read())
{
  ... do stuff
}

You would

dr.read();
... do stuff

do
{
  ... do stuff
}while(dr.read())
John MacIntyre
Peek() is quite a bit more flexible than this approach because it can be done multiple times on the same DataReader, in many places, without having to pass the "first time" results around either.
clintp
Reading the DataReader values doesn't move it ahead, the Read() method does it. You can read the values as many times as you want without moving it ahead. I'm not sure how Peek() would add any value at all. I may have missed your point. ????
John MacIntyre
This is workable, but I was wanting to consume the reader via linq instead of manually.
Gabe Moothart
You're looking to parse out the records using LINQ from the datareader? I don't get the purpose of this. LINQ is designed for a different approach than using the data reader. You LINQ over an EntitySet, a data reader is an entirely different approach...
BenAlabaster
Not using Linq-to-Sql. DbDataReader implements IEnumerable, so you can use indeed linq over a reader using rdr.Cast<DbDataRecord>() as the source. I prefer the way this abstracts the iteration.
Gabe Moothart
+2  A: 

You could create a state machine that tracks peek-mode vs regular-mode. Maybe something like this (could just toss them all into a single file called Peeker.cs or something like that):

public sealed class Peeker
{
    internal readonly PeekMode PEEKING;
    internal readonly NormalMode NORMAL;

    private ReadState _state;

    public Peeker()
    {
        PEEKING = new PeekMode(this);
        NORMAL = new NormalMode(this);

        // Start with a normal mode
        _state = NORMAL;
    }

    public object[] OnRead(IDataReader dr, bool peek)
    {
        return _state.OnRead(dr, peek);
    }

    internal void SetState(ReadState state)
    {
        _state = state;
    }
}

internal abstract class ReadState
{
    protected Peeker _peeker;

    protected ReadState(Peeker p)
    {
        _peeker = p;
    }

    public abstract object[] OnRead(IDataReader dr, bool peek);        
}

internal class PeekMode : ReadState
{
    public PeekMode(Peeker p)
        : base(p)
    {
    }

    public override object[] OnRead(IDataReader dr, bool peek)
    {
        object[] datarow = new object[dr.FieldCount];

        if (peek)
        {                
            dr.GetValues(datarow);                
        }
        else
        {
            if (dr.Read())
            {
                dr.GetValues(datarow);
                _peeker.SetState(_peeker.NORMAL);
            }
        }

        return datarow;
    }
}

internal class NormalMode : ReadState
{
    public NormalMode(Peeker p)
        : base(p)
    {
    }

    public override object[] OnRead(IDataReader dr, bool peek)
    {
        object[] datarow = new object[dr.FieldCount];

        if (peek)
        {
            if (dr.Read())
            {
                dr.GetValues(datarow);
                _peeker.SetState(_peeker.PEEKING);
            }
        }
        else
        {
            if (dr.Read())
            {
                dr.GetValues(datarow);
            }
        }

        return datarow;
    }
}

Kind of overkill, but oh well.

To use it you would just do the following:

Peeker p = new Peeker();
.
.
.
SomeDataReaderType dr = SomeCommandType.ExecuteReader();
.
.
.
// To peek
object[] myDataRow = p.OnRead(dr, true);

// or not to peek
object[] myDataRow = p.OnRead(dr, false);

Then do what you need to do with your row. There might be a better way than using an object array, but you get the point.

Good luck!

Jason Down
This is more or less what I was going for. Looks like I'd need a wrapper class so that I can keep state information. I'm not sure if it's worth the hassle, though.
Gabe Moothart
Depends how important it is. It wouldn't take too much code to do it, but it would take a bit of work (3 small classes likely).
Jason Down
Well make that 4 small classes =)
Jason Down
+3  A: 

I would suggest something similar to Jason's solution, but using a wrapper that implements IDataReader instead, so:

sealed public class PeekDataReader : IDataReader
{
    private IDataReader wrappedReader;
    private bool wasPeeked;
    private bool lastResult;

    public PeekDataReader(IDataReader wrappedReader)
    {
        this.wrappedReader = wrappedReader;
    }

    public bool Peek()
    {
        // If the previous operation was a peek, do not move...
        if (this.wasPeeked)
            return this.lastResult;

        // This is the first peek for the current position, so read and tag
        bool result = Read();
        this.wasPeeked = true;
        return result;
    }

    public bool Read()
    {
        // If last operation was a peek, do not actually read
        if (this.wasPeeked)
        {
            this.wasPeeked = false;
            return this.lastResult;
        }

        // Remember the result for any subsequent peeks
        this.lastResult = this.wrappedReader.Read();
        return this.lastResult;
    }

    public bool NextResult()
    {
        this.wasPeeked = false;
        return this.wrappedReader.NextResult();
    }

    // Add pass-through operations for all other IDataReader methods
    // that simply call on 'this.wrappedReader'
}

Note that this does require quite a bit of pass-through code for all the unaffected properties, but the benefit is that it is a generic abstraction that can 'peek' at any position in the result set without moving forward on the subsequent 'read' operation.

To use:

using (IDataReader reader = new PeekDataReader(/* actual reader */))
{
    if (reader.Peek())
    {
        // perform some operations on the first row if it exists...
    }

    while (reader.Read())
    {
        // re-use the first row, and then read the remainder...
    }
}

Note though that every 'Peek()' call will actually move to the next record if the previous operation was not also a 'Peek()'. Keeping this symmetry with the 'Read()' operation provides a simpler implementation and a more elegant API.

jerryjvl