Как получить количество строк с помощью SqlDataReader в C #

98

Мой вопрос в том, как получить количество строк, возвращаемых запросом, используя SqlDataReaderC #. Я видел несколько ответов по этому поводу, но ни один из них не был четко определен, за исключением одного, в котором говорится, что нужно выполнить цикл 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
}
Томаш Иневич
источник

Ответы:

97

Вариантов всего два:

  • Узнайте, прочитав все строки (а затем вы можете их сохранить)

  • заранее запустите специальный запрос SELECT COUNT (*).

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

И (благодаря Питу Оханлону) второй вариант безопасен только для параллелизма, когда вы используете транзакцию с уровнем изоляции Snapshot.

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

Хенк Холтерман
источник
5
Хенк прав: в DataReader нет элемента, позволяющего получить количество строк, потому что это считыватель только вперед. Лучше сначала получить счетчик, а затем выполнить запрос, возможно, в запросе с несколькими результатами, чтобы вы попадали в базу данных только один раз.
flipdoubt 05
14
Проблема со специализированным счетчиком заключается в том, что существует вероятность того, что счетчик будет отличаться от количества возвращенных строк, потому что кто-то другой изменил данные таким образом, что это приводит к количеству возвращаемых строк.
Pete OHanlon 05
1
Пит, ты прав, для этого потребуется дорогой IsolationLevel.
Хенк Холтерман
1
Спасибо вам всем! Это становится более ясным. Так что лучше: сбросить всю информацию в DataSet или запустить SQL COUNT (*), сохранить его и затем выполнить требуемый запрос? Или мы говорим о текущем подсчете и хранении всего в DataSet?
Tomasz Iniewicz
4
RepeatableReadУровень изоляции не выполняет диапазон блокировки , так что все еще позволяет запись должна быть вставлена, необходимо использовать уровень изоляции Snapshotили Serializable.
Lukazoid
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
Я сам люблю DataSet, так как это хорошо написанное и чрезвычайно полезное общее представление табличных данных. Как ни странно, я заметил, что большинство людей, которые избегают DataSet для ORM, - это те же люди, которые пытаются написать свой собственный код, чтобы он был как можно более универсальным (обычно бессмысленно).
MusiGenesis 05
5
Дэниел, «выше» - не лучший способ сослаться на другой ответ.
Хенк Холтерман
6

Вы не можете получить количество строк непосредственно из считывателя данных, потому что это так называемый курсор с пожарным шлангом, что означает, что данные считываются построчно в зависимости от выполняемого чтения. Я бы не советовал делать 2 чтения данных, потому что существует вероятность того, что данные изменились между выполнением 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;
    }
Пит Мин
источник