2009-05-21 13 views
6

Non sembra essere un metodo Peek sul DataReader in ado.net. Mi piacerebbe essere in grado di eseguire alcune elaborazioni una tantum prima di collegarmi al mio lettore, e sarebbe bello poter guardare i dati nella prima riga senza che vengano saltati dall'iterazione successiva. Qual è il modo migliore per farlo?Come si implementa una funzione Peek() su un DataReader?

Sto utilizzando uno SqlDataReader, ma preferibilmente l'implementazione sarebbe il più generale possibile (vale a dire applicabile a IDataReader o DbDataReader).

risposta

5

Vorrei suggerire qualcosa di simile a una soluzione di Jason, ma usando un wrapper che implementa IDataReader invece, in modo da:

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

Si noti che questo richiede un po 'di codice di pass-through per tutte le proprietà inalterati, ma il vantaggio è che si tratta di un'astrazione generica che può "sbirciare" in qualsiasi posizione nel set di risultati senza andare avanti nella successiva operazione di "lettura".

Per utilizzare:

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

Nota però che ogni 'Peek()' chiamata sarà effettivamente passare al record successivo se l'operazione precedente non era anche un 'Peek()'. Mantenere questa simmetria con l'operazione 'Leggi()' fornisce un'implementazione più semplice e un'API più elegante.

+1

L'esempio non funziona, poiché l'interfaccia 'IDataReader' non contiene il tuo metodo' .Peek'. Dovresti digitare esplicitamente la variabile scope usando come 'PeekDataReader' o usare' var'. – julealgon

3

Non è necessario un metodo Peek(). Puoi realizzare ciò che ti serve con un ciclo Do While.

Così, invece di

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

si sarebbe

dr.read(); 
... do stuff 

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

Peek() è un po 'più flessibile di questo approccio perché può essere eseguito più volte sullo stesso DataReader, in molti punti, senza dover passare i risultati "prima volta" in entrambi. –

+1

La lettura dei valori DataReader non lo sposta in avanti, il metodo Read() lo fa. Puoi leggere i valori tutte le volte che vuoi senza spostarlo in avanti. Non sono sicuro di come Peek() possa aggiungere alcun valore. Potrei aver perso il tuo punto. ???? –

+0

Questo è fattibile, ma volevo consumare il lettore via linq invece che manualmente. –

4

è possibile creare una macchina a stati che tiene traccia peek-mode vs regular-mode. Forse qualcosa di simile (potrebbe semplicemente li gettare in un unico file chiamato Peeker.cs o qualcosa di simile):

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

Tipo di eccessivo, ma vabbè.

Per usarlo si dovrebbe solo fare quanto segue:

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

poi fare quello che devi fare con la tua linea. Potrebbe esserci un modo migliore rispetto all'utilizzo di un array di oggetti, ma ottieni il punto.

Buona fortuna!

+0

Questo è più o meno quello che stavo per. Sembra che avrei bisogno di una classe wrapper in modo da poter mantenere le informazioni di stato. Non sono sicuro che valga la pena, comunque. –

+0

Dipende quanto sia importante. Non ci vuole troppo codice per farlo, ma ci vorrebbe un po 'di lavoro (probabilmente 3 classi piccole). –

+0

Bene rendi 4 classi piccole =) –

Problemi correlati