Jak uzyskać liczbę wierszy za pomocą SqlDataReader w C #

98

Moje pytanie brzmi, jak uzyskać liczbę wierszy zwróconych przez zapytanie przy użyciu SqlDataReaderw C #. Widziałem kilka odpowiedzi na ten temat, ale żadna nie została jasno zdefiniowana, z wyjątkiem jednej, która mówi, że należy wykonać pętlę while z Read()metodą i zwiększyć licznik.

Mój problem polega na tym, że próbuję wypełnić wielowymiarową tablicę, w której pierwszy wiersz jest nazwami nagłówków kolumn, a każdy następny wiersz ma być danymi wiersza.

Wiem, że mogę po prostu zrzucić rzeczy do kontrolki List i nie martwić się o to, ale dla własnego osobistego rozwoju i chciałbym również pobierać dane do iz tablicy, gdy wybieram i wyświetlać je w różnych formatach.

Więc myślę, że nie mogę zrobić, Read()a następnie zwiększyć ++ sposób, ponieważ oznacza to, że musiałbym otworzyć, Read()a następnie Read()ponownie otworzyć, aby uzyskać liczbę wierszy, a następnie dane kolumny.

Tylko mały przykład tego, o czym mówię:

int counter = 0;    

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

a następnie pętla for przebiegająca przez kolumny i pop

something.Read();

int dbFields = sqlRead.FieldCount;

for (int i = 0; i < dbFields; i++)
{
   // do stuff to array
}
Tomasz Iniewicz
źródło

Odpowiedzi:

97

Istnieją tylko dwie opcje:

  • Dowiedz się, czytając wszystkie wiersze (i równie dobrze możesz je przechowywać)

  • wcześniej uruchom wyspecjalizowane zapytanie SELECT COUNT (*).

Dwukrotne przejście przez pętlę DataReader jest naprawdę kosztowne, musiałbyś ponownie wykonać zapytanie.

I (dzięki Pete'owi OHanlonowi) druga opcja jest bezpieczna dla współbieżności tylko wtedy, gdy używasz transakcji z poziomem izolacji Snapshot.

Ponieważ i tak chcesz przechowywać wszystkie wiersze w pamięci, jedyną rozsądną opcją jest odczytanie wszystkich wierszy w elastycznej pamięci masowej ( List<>lub DataTable), a następnie skopiowanie danych do dowolnego formatu. Operacja w pamięci zawsze będzie znacznie wydajniejsza.

Henk Holterman
źródło
5
Henk ma rację: nie ma elementu członkowskiego DataReader, który pozwala uzyskać liczbę wierszy, ponieważ jest to czytnik tylko do przodu. Lepiej jest najpierw uzyskać liczbę, a następnie wykonać zapytanie, być może w zapytaniu z wieloma wynikami, więc trafisz do bazy danych tylko raz.
flipdoubt
14
Problem z wyspecjalizowaną liczbą polega na tym, że istnieje możliwość, że liczba będzie inna niż liczba zwróconych wierszy, ponieważ ktoś inny zmienił dane w sposób, który prowadzi do liczby zwracanych wierszy.
Pete OHanlon
1
Pete, masz rację, wymagałoby to kosztownego poziomu izolacji.
Henk Holterman
1
Dziękuję wam wszystkim! To staje się bardziej jasne. Czy więc lepiej jest zrzucić wszystkie informacje do DataSet, czy przeprowadzić przez SQL COUNT (*), zapisać je, a następnie uruchomić wymagane zapytanie? A może mówimy o liczeniu i przechowywaniu wszystkiego w DataSet?
Tomasz Iniewicz
4
RepeatableReadPoziom izolacji nie wykonuje zakres blokowania więc nadal pozwala rekordy mają być wstawione, trzeba używać poziom izolowania Snapshotlub Serializable.
Lukazoid,
10

Jeśli nie musisz pobierać całego wiersza i chcesz uniknąć podwójnego zapytania, prawdopodobnie możesz spróbować czegoś takiego:

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

Być może będziesz musiał dodać transakcję, nie mając pewności, czy ponowne użycie tego samego polecenia spowoduje automatyczne dodanie do niej transakcji ...

Pit Ming
źródło
1
Każdy może powiedzieć, czy @@ ROWCOUNT zawsze polega na ostatnim zapytaniu, które zostało uruchomione powyżej? Problemy, jeśli wiele połączeń wykonuje zapytania równolegle?
YvesR
1
Czy to konieczne sqlCon.Close();? Pomyślałem, że to usingpowinno zrobić to za Ciebie.
niebieskawy
1
nie zadziała w przypadku, gdy potrzebujemy
zliczania wierszy
8

Zgodnie z powyższym, zbiór danych lub zbiór danych wpisanych na maszynie może być dobrą tymczasową strukturą, której można użyć do filtrowania. SqlDataReader jest przeznaczony do bardzo szybkiego odczytu danych. Kiedy jesteś w pętli while (), nadal jesteś połączony z bazą danych i czeka, aż zrobisz wszystko, co robisz, aby odczytać / przetworzyć następny wynik, zanim przejdzie dalej. W takim przypadku lepszą wydajność można uzyskać, pobierając wszystkie dane, zamykając połączenie z bazą danych i przetwarzając wyniki „w trybie offline”.

Wydaje się, że ludzie nienawidzą zbiorów danych, więc powyższe można zrobić również z kolekcją silnie wpisanych obiektów.

Daniel Segan
źródło
2
Uwielbiam DataSets, ponieważ są one dobrze napisaną i niezwykle użyteczną generyczną reprezentacją danych tabelarycznych. Co dziwne, zauważyłem, że większość ludzi, którzy unikają DataSet dla ORM, to ci sami ludzie, którzy próbują napisać własny kod, aby był jak najbardziej ogólny (zwykle bezcelowo).
MusiGenesis
5
Daniel, „powyżej” nie jest dobrym sposobem na odniesienie się do innej odpowiedzi.
Henk Holterman,
6

Nie można uzyskać liczby wierszy bezpośrednio z czytnika danych, ponieważ jest to tak zwany kursor węża pożarowego - co oznacza, że ​​dane są odczytywane wiersz po wierszu na podstawie wykonywanego odczytu. Odradzałbym wykonywanie 2 odczytów danych, ponieważ istnieje możliwość, że dane uległy zmianie między wykonaniem 2 odczytów, a zatem uzyskasz różne wyniki.

Możesz wczytać dane do tymczasowej struktury i użyć ich zamiast drugiego odczytu. Alternatywnie musisz zmienić mechanizm pobierania danych i zamiast tego użyć czegoś takiego jak DataTable.

Pete OHanlon
źródło
5

aby uzupełnić odpowiedź Pit i uzyskać lepszą wydajność: uzyskaj wszystko w jednym zapytaniu i użyj metody 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();
}
Mehdi
źródło
1

Mam również do czynienia z sytuacją, w której musiałem zwrócić najwyższy wynik, ale chciałem również uzyskać łączną liczbę wierszy pasujących do zapytania. ostatecznie dochodzę do tego rozwiązania:

   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
źródło