Come ottenere il numero di righe utilizzando SqlDataReader in C #


98

La mia domanda è come ottenere il numero di righe restituite da una query utilizzando SqlDataReaderin C #. Ho visto alcune risposte su questo, ma nessuna era chiaramente definita tranne una che afferma di fare un ciclo while con Read()metodo e incrementare un contatore.

Il mio problema è che sto cercando di riempire un array multidimensionale con la prima riga che rappresenta i nomi delle intestazioni di colonna e ogni riga successiva per essere i dati della riga.

So che posso semplicemente scaricare le cose in un controllo List e non preoccuparmene, ma per la mia edificazione personale e vorrei anche estrarre i dati dall'array come scelgo e visualizzarli in diversi formati.

Quindi penso di non poter fare il modo Read()e quindi incrementare ++ perché ciò significa che dovrei aprire Read()e poi riaprire Read()per ottenere la quantità di righe e quindi i dati delle colonne.

Solo un piccolo esempio di ciò di cui sto parlando:

int counter = 0;    

while (sqlRead.Read())
{
    //get rows
    counter++
}

e poi un ciclo for per scorrere le colonne e pop

something.Read();

int dbFields = sqlRead.FieldCount;

for (int i = 0; i < dbFields; i++)
{
   // do stuff to array
}

Risposte:


96

Ci sono solo due opzioni:

  • Scoprilo leggendo tutte le righe (e poi potresti anche memorizzarle)

  • eseguire in anticipo una query SELECT COUNT (*) specializzata.

Passare due volte attraverso il ciclo DataReader è molto costoso, dovresti rieseguire la query.

E (grazie a Pete OHanlon) la seconda opzione è sicura solo per la concorrenza quando si utilizza una transazione con un livello di isolamento Snapshot.

Dal momento che si desidera comunque archiviare tutte le righe in memoria, l'unica opzione sensata è leggere tutte le righe in una memoria flessibile ( List<>o DataTable) e quindi copiare i dati in qualsiasi formato si desideri. L'operazione in memoria sarà sempre molto più efficiente.


5
Henk ha ragione: non esiste un membro del DataReader che ti permetta di ottenere il numero di righe perché è un lettore solo in avanti. È meglio ottenere prima il conteggio e quindi eseguire la query, magari in una query con più risultati in modo da colpire il database solo una volta.
flipdoubt

14
Il problema con il conteggio specializzato è che esiste la possibilità che il conteggio sia diverso dal numero di righe restituite perché qualcun altro ha modificato i dati in un modo che porta al numero di righe restituite.
Pete OHanlon,

1
Pete, hai ragione, richiederebbe un costoso IsolationLevel.
Henk Holterman,

1
Grazie a tutti! Questo sta diventando più chiaro. Quindi è meglio scaricare tutte le informazioni nel DataSet o eseguire un SQL COUNT (*), memorizzarlo e quindi eseguire la query richiesta? O stiamo parlando di conteggio parziale e archiviazione di tutto nel DataSet?
Tomasz Iniewicz

4
Un RepeatableReadlivello di isolamento non esegue il blocco dell'intervallo, quindi consente comunque di inserire i record, è necessario utilizzare un livello di isolamento di Snapshoto Serializable.
Lukazoid,

10

Se non hai bisogno di recuperare tutta la riga e vuoi evitare di fare una doppia query, puoi probabilmente provare qualcosa del genere:

using (var sqlCon = new SqlConnection("Server=127.0.0.1;Database=MyDb;User Id=Me;Password=glop;"))
      {
        sqlCon.Open();

        var com = sqlCon.CreateCommand();
        com.CommandText = "select * from BigTable";
        using (var reader = com.ExecuteReader())
        {
            //here you retrieve what you need
        }

        com.CommandText = "select @@ROWCOUNT";
        var totalRow = com.ExecuteScalar();

        sqlCon.Close();
      }

Potrebbe essere necessario aggiungere una transazione non sicura se il riutilizzo dello stesso comando aggiungerà automaticamente una transazione su di essa ...


1
Chiunque può dire se @@ ROWCOUNT fa sempre affidamento sull'ultima query eseguita sopra? Problemi se molte connessioni eseguono query in parallelo?
YvesR

1
È necessario fare sqlCon.Close();? Ho pensato che usingdovrebbe farlo per te.
bluastro

1
non funzionerà nel caso in cui sia necessario il conteggio delle righe prima di recuperare i dati dal lettore
Heemanshu Bhalla

8

Come sopra, un set di dati o un set di dati digitato potrebbe essere una buona struttura temporanea che potresti usare per filtrare. Un SqlDataReader ha lo scopo di leggere i dati molto rapidamente. Mentre sei nel ciclo while () sei ancora connesso al DB e sta aspettando che tu faccia qualunque cosa tu stia facendo per leggere / elaborare il risultato successivo prima che vada avanti. In questo caso potresti ottenere prestazioni migliori se inserisci tutti i dati, chiudi la connessione al DB ed elabori i risultati "offline".

Le persone sembrano odiare i set di dati, quindi quanto sopra potrebbe essere fatto anche con una raccolta di oggetti fortemente tipizzati.


2
Io stesso amo i DataSet, poiché sono una rappresentazione generica ben scritta ed estremamente utile di dati basati su tabelle. Stranamente, ho notato che la maggior parte delle persone che evitano il DataSet per ORM sono le stesse persone che cercano di scrivere il proprio codice per essere il più generico possibile (di solito inutilmente).
MusiGenesis

5
Daniel, "above" non è un buon modo per fare riferimento a un'altra risposta.
Henk Holterman,

6

Non è possibile ottenere un conteggio delle righe direttamente da un lettore di dati perché è quello che è noto come cursore firehose, il che significa che i dati vengono letti riga per riga in base alla lettura eseguita. Sconsiglierei di fare 2 letture sui dati perché c'è la possibilità che i dati siano cambiati tra le 2 letture e quindi otterresti risultati diversi.

Quello che potresti fare è leggere i dati in una struttura temporanea e usarla al posto della seconda lettura. In alternativa, dovrai cambiare il meccanismo con cui recuperi i dati e utilizzare invece qualcosa come un DataTable.


5

per completare la risposta Pit e per prestazioni migliori: ottieni tutto in una query e usa il metodo NextResult.

using (var sqlCon = new SqlConnection("Server=127.0.0.1;Database=MyDb;User Id=Me;Password=glop;"))
{
    sqlCon.Open();
    var com = sqlCon.CreateCommand();
    com.CommandText = "select * from BigTable;select @@ROWCOUNT;";
    using (var reader = com.ExecuteReader())
    {
        while(reader.read()){
            //iterate code
        }
        int totalRow = 0 ;
        reader.NextResult(); // 
        if(reader.read()){
            totalRow = (int)reader[0];
        }
    }
    sqlCon.Close();
}

1

Devo anche affrontare una situazione in cui avevo bisogno di restituire un risultato migliore, ma volevo anche ottenere le righe totali che corrispondevano alla query. finalmente arrivo a questa soluzione:

   public string Format(SelectQuery selectQuery)
    {
      string result;

      if (string.IsNullOrWhiteSpace(selectQuery.WherePart))
      {
        result = string.Format(
@"
declare @maxResult  int;
set @maxResult = {0};

WITH Total AS
(
SELECT count(*) as [Count] FROM {2}
)
SELECT top (@maxResult) Total.[Count], {1} FROM Total, {2}", m_limit.To, selectQuery.SelectPart, selectQuery.FromPart);
      }
      else
      {
        result = string.Format(
@"
declare @maxResult  int;
set @maxResult = {0};

WITH Total AS
(
SELECT count(*) as [Count] FROM {2} WHERE {3}
)
SELECT top (@maxResult) Total.[Count], {1} FROM Total, {2} WHERE {3}", m_limit.To, selectQuery.SelectPart, selectQuery.FromPart, selectQuery.WherePart);
      }

      if (!string.IsNullOrWhiteSpace(selectQuery.OrderPart))
        result = string.Format("{0} ORDER BY {1}", result, selectQuery.OrderPart);

      return result;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.