2011-12-15 6 views
7

Guardate questa:C#: Devo usare "if" invece di "while" quando un Reader ha un solo elemento?

myReader = cmd.ExecuteReader(); 
if (myReader.HasRows) 
{ 
    while (myReader.Read()) 
    { 
     info = myReader.GetString("info"); 
     ... 
    } 
} 
..... 

Bene, Io non so perché, ma quando compilo e utilizzarlo in un altro PC, esplode a causa del mentre. Ma se lo faccio:

myReader = cmd.ExecuteReader(); 
if (myReader.HasRows) 
{ 
    if (myReader.Read()) <------------------- NOTICE THE IF 
    { 
     info = myReader.GetString("info"); 
     ... 
    } 
} 
..... 

... funzionerà. Si noti che so che avrò un solo elemento ogni volta, a causa della logica del programma.

È OK farlo? Sembra orribile e forse non è una buona pratica (?). Chiedo il tuo orientamento.

EDIT: posterò un po 'di codice, perché potrebbe aiutare a trovare i miei errori:

while (myReader.Read()) 
{ 
    info = myReader.GetString("info"); 
    ... 


    /** 
     * Write the relevant info in the LOGS 
     */ 
     connection.Close();  <---------- :S 
     connection.Open();  <---------- if I don´t do this I got some problems with the connection!!!! 
     string queryLog = "INSERT INTO ....; 
     MySqlCommand cmdLog = new MySqlCommand(queryLog, connection); 
     cmdLog.ExecuteNonQuery(); 
} 
+5

Che cosa significa "esplode"? – SLaks

+0

Sembra che ci sia un problema di fondo che probabilmente dovresti capire piuttosto che evitarlo come hai fatto tu. Cosa significa esplodere? Viene lanciata un'eccezione? Che eccezione? –

+0

È un'eccezione, non ho a portata di mano in questo momento, ma riguarda le connessioni. Ho modificato il post per aggiungere del codice a riguardo. – Kani

risposta

13

faccio questo ogni volta che mi aspetto solo una riga, niente di sbagliato con esso.

Se mi aspetto un valore singolo, utilizzare invece ExecuteScalar. Ciò restituirà semplicemente il valore contenuto nella prima colonna della prima riga del set di risultati restituito, di solito il set ha solo una colonna e una riga, quindi non si tende a notare questo.

Anche io tendo a non disturbare con if (myReader.HasRows) come if (myReader.Read()) copre questo.

ho idea del perché il while si provoca problemi ... sto solo offrendo il mio parere sull'utilizzo del if sopra il while se non si aspettava più righe.

+0

Interessante. Fino ad ora non mi ero reso conto che ExecuteScalar poteva restituire una stringa. Ho sempre pensato a uno scalare come un numero perché è generalmente usato in questo modo in fisica e matematica, ed ExecuteScalar viene spesso utilizzato per restituire un conteggio. –

+0

@EricJ. Sì, il nome del metodo non è del tutto fantastico, ma personalmente ho letto lo scalare come un "valore singolo", quindi non ho mai considerato il tuo punto di vista fino ad ora :-) –

+0

È coerente con la definizione perl di "scalare" , che intendo significare "elemento singolo" in contrapposizione a una lista. – recursive

3

Senza utilizzare while, non è possibile rilevare una condizione imprevista in cui il lettore ha più elementi di quanto si pensi debba avere. I sistemi cambiano nel tempo e ipotesi come "il lettore avrà sempre e solo un elemento" tendono a diventare errate con l'evolversi del sistema.

Se è importante per la funzione del sistema o della funzione del codice che si sta scrivendo che ha assunto questa ipotesi, utilizzerei comunque un while e includo un codice di compilazione solo di debug che asserisce che davvero ha ricevuto solo un elemento.

+0

Buon punto, e non fa molto male usare While invece di If. Nessun problema di prestazioni di cui sono a conoscenza in quest'area. –

+0

Grazie. Credo che tu abbia ragione sul fatto che il sistema potrebbe cambiare. Parlerò con me le università per vedere se i requisiti non cambieranno, ** mai ** – Kani

3

Penso che l'utilizzo di if chiarisca ai lettori del codice che ci si aspetta esattamente un elemento. È possibile aggiungere un'asserzione dopo la fine di rilevare gli errori logici, in questo modo:

if (myReader.Read()) { 
    info = myReader.GetString("info"); 
    ... 
} 
Debug.Assert(!myReader.Read(), "Reader has more elements than expected."); 
+0

+1 Buona chiamata sull'affermazione. –

-5

questo codice è necessario aggiungere il seguente per farlo tornare o meno restituire le righe

myReader = cmd.ExecuteReader(); 
    myReader.Read(); 
    if (myReader.HasRows) 
    {  
    while (myReader.Read()) 
    { 
     info = myReader.GetString("info"); 
    } 
    } 
+5

Questo codice non è eccezionale ... si itera il lettore prima di iniziare a leggere, in modo da perdere sempre il primo valore restituito. L'OP è interessato solo a una riga, quindi questo non è effettivamente sufficiente. –

+0

Capisco che ... gli stavo mostrando quello che poteva fare .. tocca a lui capire quale usare .. leggendo dal suo codice non si può dire se sta per restituire una riga o più .. quindi la tua il voto è un po 'prematuro .. – MethodMan

+8

Il downvote era giustificato a mio avviso, anche con più righe si salta la prima riga per ogni set di risultati restituito - Non ho mai visto questo necessario in natura e sarebbe fastidioso eseguire il debug. L'OP dice anche che si aspetta "solo un elemento". Gli stai mostrando anche il codice che ha già, quindi non riesco a vedere quale valore è stato aggiunto oltre a un potenziale bug. –

0

Penso che si dovrebbe usa ancora il tempo anche se ti aspetti una sola riga, quindi usa linq per esprimere l'aspettativa di una singola riga quando la chiami. Puoi usare una coroutine per esprimerlo bene.

public IEnumerable<string> GetInfo() 
{ 
    // make command and reader... 
    while(reader.Read() 
    { 
     yield return reader.GetString("info"); 
    } 
} 

Quindi il chiamante deve utilizzare il metodo Single() per esprimere l'aspettativa di una sola riga.

var info = GetInfo().Single(); 
1

Disponi correttamente la connessione e il lettore? Utilizzare Using, come:

using (myReader = cmd.ExecuteReader()) 
{ 
    if (myReader.HasRows) 
    { 
     while (myReader.Read()) 
     { 
      info = myReader.GetString("info"); 
      ... 
     } 
    } 
} 
3

È possibile infatti utilizzare if, ma si dovrebbe anche essere in grado di utilizzare while in un caso come questo senza un errore. Basta cambiarlo in if, ma se si fa la stessa cosa in un caso in cui è possibile avere più di una riga, si otterrà lo stesso errore. È quindi importante esaminare il motivo per cui l'eccezione stava accadendo.

Esaminiamo il codice:

while (myReader.Read()) 
{ 
    info = myReader.GetString("info"); 
    ... 

Fin qui tutto bene.

 /** 
     * Write the relevant info in the LOGS 
     */ 
     connection.Close();  <---------- :S 
     connection.Open();  <---------- if I don´t do this I got some problems with the connection!!!! 

Oh uh! Hai risolto i tuoi "problemi", ma sapevi quali erano quei "problemi"? In realtà è qui che hai rotto il tuo while.

Al punto in cui ho detto "Fin qui tutto bene", questo è ciò che avete succedendo:

  1. si ha un oggetto di connessione. È aperto".
  2. Si dispone di un oggetto lettore di dati. Sta recuperando dati dalla connessione.

Ora, dalle specifiche di IDbCommand.ExecuteReader (http://msdn.microsoft.com/en-us/library/68etdec0.aspx), il collegamento è in uso per quel lettore. Non ti è permesso fare nulla con quella connessione eccetto chiuderla, finché non chiami Close() sul lettore, esplicitamente o tramite Dispose() come accadrebbe se il lettore fosse assegnato in un blocco using.

Ora, rigorosamente questo non è sempre vero. Una determinata implementazione è sempre consentita a fornire più di un'interfaccia promessa (indipendentemente dalla firma dell'interfaccia o dalla documentazione). Molte implementazioni "rilasceranno" la connessione per il riutilizzo una volta che Read() restituisce false (l'unica che abbia mai implementato), SQLServer 2005 ha un'opzione MultipleActiveResultSets=True che consente alla stessa connessione di supportare simultaneamente più datareader contemporaneamente (ci sono aspetti negativi). Tuttavia, a parte queste distorsioni delle regole, l'unica cosa che possiamo assolutamente fare con la nostra connessione in questo momento è chiamare il numero Close().

Per questo motivo, quando si è tentato di chiamare cmdLog.ExecuteNonQuery() si sono verificati "alcuni problemi" sotto forma di un messaggio di errore, perché si stava tentando di utilizzare una connessione in uso per qualcos'altro.

Quindi, "l'hai risolto" facendo l'unica cosa che potevi fare con la connessione - chiudendola - e poi aprendola di nuovo. A tutti gli effetti, avresti una nuova connessione!

Ora, quando si torna al while, si chiama di nuovo myReader.Read().Questo metodo legge dal flusso di dati provenienti dalla connessione e restituisce false (se non ci sono altri risultati) o crea un set di campi in base a ciò che legge da quel flusso.

Ma non è così, perché hai chiuso quella connessione. Quindi "esplode" con un'eccezione perché stai cercando di leggere da una connessione chiusa e questo non può essere fatto.

Ora, quando hai sostituito questo while con un if non sei mai tornato all'operazione Read() che hai interrotto, quindi il codice funziona di nuovo.

Ma cosa hai intenzione di fare se hai mai bisogno di farlo con un set di risultati che ha più di una riga?

È possibile utilizzare MARS. Vedi http://msdn.microsoft.com/en-us/library/h32h3abf%28v=vs.80%29.aspx per quello. Francamente lo eviterei a meno che qualcosa non ottenga più da questo; ha alcune implicazioni sottili che possono essere davvero confuse se le colpisci.

È possibile posticipare l'operazione successiva fino a dopo la chiusura del lettore. In questo caso, questo sarebbe solo un risparmio minore di togliere il superfluo Open() e Close():

using(myReader = cmd.ExecuteReader()) 
{ 
    while(myReader.Read()) 
    { 
    string info = myReader.GetString("info"); 
    /* Do stuff */ 
    using(SqlConnection logConn = new SqlConnection(...)) 
    { 
     logConn.Open(); 
     string queryLog = "INSERT INTO ....; 
     MySqlCommand cmdLog = new MySqlCommand(queryLog, connection); 
     cmdLog.ExecuteNonQuery(); 
    } 
    } 
} 

Ora, può sembrare uno spreco di avere due connessioni piuttosto che uno qui. Tuttavia:

  1. Il pooling rende la creazione di connessioni piuttosto economiche.
  2. Lo stiamo rilasciando in piscina non appena facciamo il ExecuteNonQuery(), quindi se c'è un sacco di questo in corso su molti thread il numero totale di connessioni in movimento sarà probabilmente inferiore al doppio del numero di filettature. D'altra parte, se c'è solo un thread che fa questo, allora c'è solo una connessione in più, quindi a chi importa.
  3. Chiusura di una connessione quando era parzialmente attraverso la fornitura di un datareader potrebbe aver dato una pulizia extra da fare prima di tornare al pool, quindi potresti benissimo usare due connessioni (non so di SQLServer, ma so che alcuni database richiedono più lavoro prima che possano tornare in piscina in questi casi).

In sintesi, non è possibile utilizzare una connessione senza riutilizzarla, si tratta di un bug out-and-out. Di norma non è possibile avere più di una operazione sulla stessa connessione alla volta. L'utilizzo dell'opzione MultipleActiveResultSets con SQLServer2005 consente di farlo, sebbene abbia delle complicazioni. In genere, se si desidera eseguire più di una cosa DB alla volta, è necessario utilizzare più di una connessione.

Detto questo, è comunque possibile utilizzare if piuttosto che while, ma farlo perché è ciò che ha senso, al momento, piuttosto che per correggere un bug che non capisci e farlo tornare qualche tempo, quando if isn 'un'opzione.

+0

Fantastico! Grazie. – Kani

Problemi correlati