Wednesday, May 30, 2012

PeekDataReader

For a current project, I needed to write an XML file for a batch transaction process. It just so happens that several transactions needed to be wrapped within a single batch element for a single order. What I needed was a simple extension class that wrapped SqlDataReader and allowed me to “peek” at the next row in the result set to check if the transaction was still within the current batch.
While doing a brief search, I found the following helper on Stack Overflow (SO). The correct way to do this is by implementing IDataReader as you cannot extend SqlDataReader. This is because SqlDataReader’s type constructor is marked internal in System.Data.dll and cannot be inherited.
The implementation on SO did not contain the pass-thru attributes and methods needed to implement IDataReader, IDataRecord, and IDisposable, so I decided to post this on my blog. Hope this helps someone out there!
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();
        }

        #region IDataRecord pass-thru attributes and methods
        public int FieldCount { get { return this.wrappedReader.FieldCount; } }
        public object this[int i] { get { return this.wrappedReader[i]; } }
        public object this[string name] { get { return this.wrappedReader[name]; } }
        public bool GetBoolean(int i) { return this.wrappedReader.GetBoolean(i);}
        public byte GetByte(int i) { return this.wrappedReader.GetByte(i); }
        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
            return this.wrappedReader.GetBytes(i,fieldOffset,buffer,bufferoffset,length);}
        public char GetChar(int i) { return this.wrappedReader.GetChar(i); }
        public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            return this.wrappedReader.GetChars(i,fieldoffset,buffer,bufferoffset,length);
        }
        public IDataReader GetData(int i)
        {
            return this.wrappedReader.GetData(i);
        }
        public string GetDataTypeName(int i) { return this.wrappedReader.GetDataTypeName(i); }
        public DateTime GetDateTime(int i) { return this.wrappedReader.GetDateTime(i); }
        public decimal GetDecimal(int i) { return this.wrappedReader.GetDecimal(i); }
        public double GetDouble(int i) { return this.wrappedReader.GetDouble(i); }
        public Type GetFieldType(int i) { return this.wrappedReader.GetFieldType(i); }
        public float GetFloat(int i) { return this.wrappedReader.GetFloat(i); }
        public Guid GetGuid(int i) { return this.wrappedReader.GetGuid(i); }
        public short GetInt16(int i) { return this.wrappedReader.GetInt16(i); }
        public int GetInt32(int i) { return this.wrappedReader.GetInt32(i); }
        public long GetInt64(int i) { return this.wrappedReader.GetInt64(i); }
        public string GetName(int i) { return this.wrappedReader.GetName(i); }
        public int GetOrdinal(string name) { return this.wrappedReader.GetOrdinal(name); }
        public string GetString(int i) { return this.wrappedReader.GetString(i); }
        public object GetValue(int i) { return this.wrappedReader.GetValue(i); }
        public int GetValues(object[] values) { return this.wrappedReader.GetValues(values); }
        public bool IsDBNull(int i) { return this.wrappedReader.IsDBNull(i); }
        #endregion

        #region IDataReader pass-thru attributes and methods
        public int Depth { get { return this.wrappedReader.Depth; } }
        public bool IsClosed { get { return this.wrappedReader.IsClosed; } }
        public int RecordsAffected { get { return this.wrappedReader.RecordsAffected; } }
        public void Close() { this.wrappedReader.Close(); }
        public DataTable GetSchemaTable() { return this.wrappedReader.GetSchemaTable(); }
        #endregion

        #region IDisposable
        public void Dispose() { this.wrappedReader.Dispose(); }
        #endregion
    }


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