Як отримати кількість рядків за допомогою SqlDataReader в C #


98

Моє питання полягає в тому, як отримати кількість рядків, повернутих запитом, використовуючи SqlDataReaderв C #. Я бачив деякі відповіді з цього приводу, але жодна з них не була чітко визначена, за винятком тієї, яка передбачає виконання циклу while з Read()методом та збільшення лічильника.

Моя проблема полягає в тому, що я намагаюся заповнити багатовимірний масив, причому перший рядок - це імена заголовків стовпців, а кожен рядок після цього - це дані рядка.

Я знаю, що я можу просто скидати матеріали в елемент керування List і не турбуватися про це, але для мого особистого побудови, і я також хотів би витягувати дані в масив і з нього, коли вибираю та відображаю їх у різних форматах.

Тому я думаю, що я не можу зробити спосіб, Read()а потім збільшити ++, тому що це означає, що мені доведеться відкривати, Read()а потім Read()знову відкривати, щоб отримати кількість рядків, а потім даних стовпців.

Лише невеликий приклад того, про що я говорю:

int counter = 0;    

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

а потім цикл for, який проходить через стовпці та спливає

something.Read();

int dbFields = sqlRead.FieldCount;

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

Відповіді:


96

Є лише два варіанти:

  • Дізнайтеся, прочитавши всі рядки (а потім ви могли б їх також зберегти)

  • заздалегідь запустіть спеціалізований запит SELECT COUNT (*).

Двічі пройти цикл DataReader - це дійсно дорого, вам доведеться повторно виконати запит.

І (завдяки Піту Оханлону) другий варіант є безпечним лише для одночасності, коли ви використовуєте транзакцію з рівнем ізоляції Snapshot.

Оскільки ви все одно хочете зберегти всі рядки в пам'яті, єдиним розумним варіантом є прочитати всі рядки в гнучкому сховищі ( List<>або DataTable), а потім скопіювати дані у будь-який потрібний формат. Робота в пам'яті завжди буде набагато ефективнішою.


5
Хенк має рацію: немає жодного члена DataReader, який дозволяє отримувати кількість рядків, оскільки це лише програма для читання вперед. Вам краще спочатку зробити підрахунок, а потім виконати запит, можливо, у запиті з кількома результатами, щоб ви потрапили в базу даних лише один раз.
flipdoubt

14
Проблема спеціалізованого підрахунку полягає в тому, що підрахунок може відрізнятися від кількості повернутих рядків, оскільки хтось інший змінив дані таким чином, що призводить до повернення кількості рядків.
Піт ОХенлон,

1
Піт, ти маєш рацію, для цього знадобиться дорогий рівень ізоляції.
Хенк Холтерман

1
Дякую вам всім! Це стає все зрозумілішим. Тож чи краще скидати всю інформацію до набору даних або запускати SQL SQL (*), зберігати її, а потім запускати необхідний запит? Або ми говоримо про запущений підрахунок і збереження всього в DataSet?
Томаш Іневич

4
Рівень RepeatableReadізоляції не виконує блокування діапазону, тому він все одно дозволяє вставляти записи, вам потрібно використовувати рівень ізоляції Snapshotабо Serializable.
Луказоїд

10

Якщо вам не потрібно отримувати весь рядок і хочете уникнути подвійного запиту, ви можете спробувати щось подібне:

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

Можливо, вам доведеться додати транзакцію, не впевнену, чи повторне використання тієї самої команди автоматично додасть транзакцію до неї ...


1
Будь-хто може сказати, чи @@ ROWCOUNT завжди покладається на останній запит, який працює вище? Проблеми, якщо багато з'єднань виконують запити паралельно?
YvesR

1
Чи потрібно це робити sqlCon.Close();? Я думав, що це usingмає зробити для вас.
синюватий

1
це не буде працювати в тому випадку, коли нам потрібен підрахунок рядків перед тим, як отримувати дані з пристрою для читання
Хеманшу Бхалла,

8

Як зазначено вище, набір даних або набраний набір даних може бути гарною тимчасовою структурою, яку ви можете використовувати для фільтрації. SqlDataReader призначений для зчитування даних дуже швидко. Поки ви знаходитесь у циклі while (), ви все ще підключені до БД, і вона чекає, коли ви зробите все, що робите, щоб прочитати / обробити наступний результат, перш ніж він рухається далі. У цьому випадку ви можете отримати кращу продуктивність, якщо втягнете всі дані, закриєте з'єднання з БД і обробляєте результати "в автономному режимі".

Здається, люди ненавидять набори даних, тому вищезазначене можна зробити і з колекцією сильно набраних об’єктів.


2
Я сам люблю DataSets, оскільки вони є добре написаним та надзвичайно корисним загальним поданням табличних даних. Як не дивно, я помітив, що більшість людей, які уникають DataSet для ORM - це ті самі люди, які намагаються написати власний код, щоб бути якомога загальнішим (як правило, безглуздо).
MusiGenesis

5
Даніель, "вище" - це не найкращий спосіб посилатися на іншу відповідь.
Хенк Холтерман,

6

Ви не можете отримати підрахунок рядків безпосередньо з пристрою для зчитування даних, оскільки це так званий курсор fire-fire - це означає, що дані зчитуються по черзі на основі читання, яке виконується. Я б не радив робити 2 читання даних, оскільки існує можливість того, що дані змінилися між двома читаннями, і, таким чином, ви отримаєте різні результати.

Що ви могли б зробити, це прочитати дані у тимчасову структуру та використовувати їх замість другого зчитування. Крім того, вам потрібно буде змінити механізм отримання даних і замість цього використовувати щось на зразок DataTable.


5

щоб заповнити відповідь Pit та для кращого виконання: отримайте все в одному запиті та використовуйте метод 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

Я також стикаюся з ситуацією, коли мені потрібно було повернути найкращий результат, але я також хотів отримати загальну кількість рядків, які відповідають запиту. я нарешті дійшов до цього рішення:

   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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.