简体   繁体   中英

How do I implement a Peek() function on a DataReader?

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 (ie apply to IDataReader or DbDataReader ).

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.

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!

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM