Como obter o número de linhas usando SqlDataReader em C #

98

Minha pergunta é como obter o número de linhas retornadas por uma consulta SqlDataReaderem C #. Já vi algumas respostas sobre isso, mas nenhuma foi claramente definida, exceto uma que afirma fazer um loop while com o Read()método e incrementar um contador.

Meu problema é que estou tentando preencher uma matriz multidimensional com a primeira linha sendo os nomes dos cabeçalhos das colunas e cada linha depois disso sendo os dados da linha.

Sei que posso simplesmente despejar as coisas em um controle List e não me preocupar com isso, mas para minha própria edificação pessoal e também gostaria de inserir e retirar os dados do array conforme eu escolho e exibi-lo em diferentes formatos.

Então eu acho que não posso fazer o modo Read()e incrementar ++ porque isso significa que eu teria que abrir Read()e abrir Read()novamente para obter a quantidade de linhas e depois os dados da coluna.

Apenas um pequeno exemplo do que estou falando:

int counter = 0;    

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

e um loop for para percorrer as colunas e estourar

something.Read();

int dbFields = sqlRead.FieldCount;

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

Respostas:

96

Existem apenas duas opções:

  • Descubra lendo todas as linhas (e então você também pode armazená-las)

  • execute uma consulta SELECT COUNT (*) especializada de antemão.

Percorrer duas vezes o loop DataReader é muito caro, você teria que executar novamente a consulta.

E (graças a Pete OHanlon) a segunda opção só é segura para simultaneidade quando você usa uma transação com um nível de isolamento de instantâneo.

Já que você deseja acabar armazenando todas as linhas na memória de qualquer maneira, a única opção sensata é ler todas as linhas em um armazenamento flexível ( List<>ou DataTable) e, em seguida, copiar os dados para qualquer formato desejado. A operação na memória sempre será muito mais eficiente.

Henk Holterman
fonte
5
Henk está certo: não há nenhum membro do DataReader que permita obter o número de linhas porque é um leitor somente de encaminhamento. É melhor você obter primeiro a contagem e, em seguida, executar a consulta, talvez em uma consulta de vários resultados, para que você acesse o banco de dados apenas uma vez.
flipdoubt
14
O problema com a contagem especializada é que existe a possibilidade de a contagem ser diferente do número de linhas retornadas porque outra pessoa alterou os dados de uma maneira que leva ao número de linhas retornadas.
Pete OHanlon,
1
Pete, você está certo, exigiria um IsolationLevel caro.
Henk Holterman,
1
Obrigado a todos! Isso está ficando mais claro. Portanto, é melhor despejar todas as informações no DataSet ou executar um SQL COUNT (*), armazená-lo e, em seguida, executar a consulta necessária? Ou estamos falando sobre contagem de execução e armazenamento de tudo no DataSet?
Tomasz Iniewicz
4
Um RepeatableReadnível de isolamento não executa bloqueio de intervalo, portanto, ainda permite que os registros sejam inseridos; você precisa estar usando um nível de isolamento de Snapshotou Serializable.
Lukazoid de
10

Se você não precisa recuperar todas as linhas e deseja evitar fazer uma consulta dupla, provavelmente pode tentar algo assim:

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

Você pode ter que adicionar uma transação sem ter certeza se a reutilização do mesmo comando adicionará automaticamente uma transação nele ...

Pit Ming
fonte
1
Alguém pode dizer se @@ ROWCOUNT sempre depende da última consulta executada acima? Problemas se muitas conexões executam consultas paralelas?
YvesR
1
É necessário fazer sqlCon.Close();? Achei que isso usingdeveria bastar para você.
azulado de
1
não funcionará no caso de precisarmos da contagem de linhas antes de recuperar os dados do leitor
Heemanshu Bhalla
8

Conforme acima, um conjunto de dados ou conjunto de dados tipado pode ser uma boa estrutura temorária que você pode usar para fazer sua filtragem. Um SqlDataReader serve para ler os dados muito rapidamente. Enquanto você está no loop while (), você ainda está conectado ao banco de dados e ele está esperando que você faça o que estiver fazendo para ler / processar o próximo resultado antes de prosseguir. Nesse caso, você pode obter um melhor desempenho se extrair todos os dados, fechar a conexão com o banco de dados e processar os resultados "offline".

As pessoas parecem odiar conjuntos de dados, então o que foi dito acima também poderia ser feito com uma coleção de objetos fortemente tipados.

Daniel Segan
fonte
2
Eu mesmo adoro DataSets, pois eles são uma representação genérica bem escrita e extremamente útil de dados baseados em tabelas. Estranhamente, notei que a maioria das pessoas que evitam o DataSet para ORM são as mesmas pessoas que tentam escrever seu próprio código para ser o mais genérico possível (geralmente sem sentido).
MusiGenesis
5
Daniel, 'acima' não é uma boa maneira de fazer referência a outra resposta.
Henk Holterman
6

Você não pode obter uma contagem de linhas diretamente de um leitor de dados porque é o que é conhecido como cursor firehose - o que significa que os dados são lidos linha por linha com base na leitura realizada. Eu não aconselho fazer 2 leituras nos dados porque existe a possibilidade de que os dados tenham mudado entre as duas leituras e, portanto, você obteria resultados diferentes.

O que você pode fazer é ler os dados em uma estrutura temporária e usá-la no lugar da segunda leitura. Como alternativa, você precisará alterar o mecanismo pelo qual você recupera os dados e usar algo como uma DataTable.

Pete OHanlon
fonte
5

para completar a resposta do Pit e para um melhor desempenho: obtenha tudo em uma consulta e use o método 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
fonte
1

Também enfrento uma situação em que preciso retornar um resultado superior, mas também quero obter o total de linhas que correspondam à consulta. Finalmente chego a esta solução:

   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
fonte