So ermitteln Sie die Anzahl der Zeilen mit SqlDataReader in C #

98

Meine Frage ist, wie man die Anzahl der von einer Abfrage zurückgegebenen Zeilen mit SqlDataReaderC # erhält . Ich habe einige Antworten dazu gesehen, aber keine war klar definiert, außer einer, die angibt, eine while-Schleife mit der Read()Methode durchzuführen und einen Zähler zu erhöhen.

Mein Problem ist, dass ich versuche, ein mehrdimensionales Array zu füllen, wobei die erste Zeile die Spaltenüberschriften und jede Zeile danach die Zeilendaten sind.

Ich weiß, dass ich das Zeug einfach in ein List-Steuerelement kopieren kann und mich nicht darum kümmern muss, sondern für meine persönliche Bearbeitung, und ich möchte die Daten auch in das Array hinein- und herausziehen, während ich sie auswähle und in verschiedenen Formaten anzeige.

Also denke ich, ich kann das nicht tun Read() und dann ++ inkrementieren, weil das bedeutet, dass ich öffnen Read()und dann Read()wieder öffnen müsste , um die Anzahl der Zeilen und dann die Spaltendaten zu erhalten.

Nur ein kleines Beispiel dafür, wovon ich spreche:

int counter = 0;    

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

und dann eine for-Schleife, um durch die Spalten zu laufen und zu popen

something.Read();

int dbFields = sqlRead.FieldCount;

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

Antworten:

96

Es gibt nur zwei Möglichkeiten:

  • Finden Sie es heraus, indem Sie alle Zeilen lesen (und sie dann genauso gut speichern)

  • Führen Sie zuvor eine spezielle SELECT COUNT (*) - Abfrage aus.

Das zweimalige Durchlaufen der DataReader-Schleife ist sehr teuer. Sie müssten die Abfrage erneut ausführen.

Und (dank Pete OHanlon) ist die zweite Option nur dann parallelsicher, wenn Sie eine Transaktion mit einer Snapshot-Isolationsstufe verwenden.

Da Sie ohnehin alle Zeilen im Speicher speichern möchten, ist es nur sinnvoll, alle Zeilen in einem flexiblen Speicher ( List<>oder DataTable) zu lesen und die Daten dann in ein beliebiges Format zu kopieren. Der In-Memory-Betrieb wird immer viel effizienter sein.

Henk Holterman
quelle
5
Henk hat recht: Es gibt kein Mitglied des DataReader, mit dem Sie die Anzahl der Zeilen ermitteln können, da es sich nur um einen Vorwärtsleser handelt. Es ist besser, zuerst die Anzahl zu ermitteln und dann die Abfrage auszuführen, möglicherweise in einer Abfrage mit mehreren Ergebnissen, sodass Sie die Datenbank nur einmal aufrufen.
Flipdoubt
14
Das Problem mit der speziellen Anzahl besteht darin, dass die Anzahl möglicherweise von der Anzahl der zurückgegebenen Zeilen abweicht, da jemand anderes die Daten so geändert hat, dass die Anzahl der zurückgegebenen Zeilen erreicht wird.
Pete OHanlon
1
Pete, du hast recht, es würde eine teure IsolationLevel erfordern.
Henk Holterman
1
Danke euch allen! Dies wird immer deutlicher. Ist es also besser, alle Informationen in das DataSet zu kopieren oder einen SQL COUNT (*) zu durchlaufen, zu speichern und dann die erforderliche Abfrage auszuführen? Oder sprechen wir über das Ausführen von count und das Speichern von allem im DataSet?
Tomasz Iniewicz
4
Eine RepeatableReadIsolationsstufe führt keine Bereichssperre durch, sodass weiterhin Datensätze eingefügt werden können. Sie müssen eine Isolationsstufe von Snapshotoder verwenden Serializable.
Lukazoid
10

Wenn Sie nicht die gesamte Zeile abrufen müssen und keine doppelte Abfrage durchführen möchten, können Sie wahrscheinlich Folgendes versuchen:

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

Möglicherweise müssen Sie eine Transaktion hinzufügen, die nicht sicher ist, ob bei erneuter Verwendung desselben Befehls automatisch eine Transaktion hinzugefügt wird ...

Pit Ming
quelle
1
Kann jeder sagen, ob sich @@ ROWCOUNT immer auf die letzte oben ausgeführte Abfrage verlässt? Probleme, wenn viele Verbindungen Abfragen parallel ausführen?
YvesR
1
Muss es getan werden sqlCon.Close();? Ich dachte, das usingsollte es für dich tun.
bläulich
1
Es wird nicht funktionieren, wenn wir eine Zeilenanzahl benötigen, bevor wir Daten vom Leser
abrufen
8

Wie oben beschrieben, kann ein Datensatz oder ein typisierter Datensatz eine gute temporäre Struktur sein, mit der Sie Ihre Filterung durchführen können. Ein SqlDataReader soll die Daten sehr schnell lesen. Während Sie sich in der while () - Schleife befinden, sind Sie immer noch mit der Datenbank verbunden und sie wartet darauf, dass Sie alles tun, was Sie tun, um das nächste Ergebnis zu lesen / verarbeiten, bevor es weitergeht. In diesem Fall erzielen Sie möglicherweise eine bessere Leistung, wenn Sie alle Daten abrufen, die Verbindung zur Datenbank schließen und die Ergebnisse "offline" verarbeiten.

Die Leute scheinen Datensätze zu hassen, daher könnte das oben Genannte auch mit einer Sammlung stark typisierter Objekte geschehen.

Daniel Segan
quelle
2
Ich liebe DataSets selbst, da sie eine gut geschriebene und äußerst nützliche generische Darstellung tabellenbasierter Daten sind. Seltsamerweise habe ich festgestellt, dass die meisten Leute, die das DataSet für ORM meiden, dieselben Leute sind, die versuchen, ihren eigenen Code so allgemein wie möglich zu schreiben (normalerweise sinnlos).
MusiGenesis
5
Daniel, 'oben' ist kein guter Weg, um auf eine andere Antwort zu verweisen.
Henk Holterman
6

Sie können die Anzahl der Zeilen nicht direkt von einem Datenleser abrufen, da es sich um einen sogenannten Firehose-Cursor handelt. Dies bedeutet, dass die Daten zeilenweise basierend auf dem durchgeführten Lesevorgang gelesen werden. Ich würde davon abraten, zwei Lesevorgänge an den Daten durchzuführen, da sich die Daten möglicherweise zwischen den beiden Lesevorgängen geändert haben und Sie daher unterschiedliche Ergebnisse erhalten.

Sie können die Daten in eine temporäre Struktur einlesen und diese anstelle des zweiten Lesevorgangs verwenden. Alternativ müssen Sie den Mechanismus ändern, mit dem Sie die Daten abrufen, und stattdessen eine Datentabelle verwenden.

Pete OHanlon
quelle
5

Um die Pit-Antwort zu vervollständigen und eine bessere Leistung zu erzielen, erhalten Sie alles in einer Abfrage und verwenden Sie die NextResult-Methode.

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();
}
mehdi
quelle
1

Ich habe auch eine Situation, in der ich ein Top-Ergebnis zurückgeben musste, aber auch die Gesamtzahl der Zeilen erhalten wollte, die mit der Abfrage übereinstimmen. Ich komme schließlich zu dieser Lösung:

   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;
    }
Pit Ming
quelle