如何在DataReader上实现Peek()函数?

时间:2009-05-21 17:42:51

标签: c# .net ado.net

ado.net中的DataReader似乎没有Peek方法。我希望能够在循环阅读器之前执行一些一次性处理,能够查看第一行中的数据而不会导致后续迭代跳过它会很好。实现这一目标的最佳方法是什么?

我使用的是SqlDataReader,但最好是实施尽可能一般(即适用于IDataReaderDbDataReader)。

3 个答案:

答案 0 :(得分:5)

我会建议类似于Jason的解决方案,但使用实现IDataReader的包装器,所以:

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

请注意,对于所有未受影响的属性,这确实需要相当多的传递代码,但好处是它是一个通用抽象,可以“窥视”结果集中的任何位置,而无需在后续步骤中向前移动'读'操作。

使用:

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

请注意,如果前一个操作不是'Peek()',那么每个'Peek()'调用实际上都会移动到下一个记录。使用'Read()'操作保持这种对称性可以提供更简单的实现和更优雅的API。

答案 1 :(得分:4)

您可以创建一个跟踪peek-mode与regular-mode的状态机。也许这样的事情(可以把它们全部扔进一个名为Peeker.cs的文件或者类似的东西):

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

有点矫枉过正,但是很好。

要使用它,您只需执行以下操作:

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

然后执行您对行所需的操作。可能有一种比使用对象数组更好的方法,但是你明白了。

祝你好运!

答案 2 :(得分:3)

您不需要Peek()方法。您可以使用Do While循环完成所需的操作。

所以而不是

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

你会

dr.read();
... do stuff

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