Comment obtenir le nombre de lignes à l'aide de SqlDataReader en C #

98

Ma question est de savoir comment obtenir le nombre de lignes renvoyées par une requête en utilisant SqlDataReaderen C #. J'ai vu quelques réponses à ce sujet, mais aucune n'a été clairement définie à l'exception de celle qui stipule de faire une boucle while avec une Read()méthode et d'incrémenter un compteur.

Mon problème est que j'essaie de remplir un tableau multidimensionnel avec la première ligne étant les noms d'en-tête de colonne et chaque ligne après cela pour les données de ligne.

Je sais que je peux simplement vider le contenu dans un contrôle de liste et ne pas m'en soucier, mais pour ma propre édification personnelle et j'aimerais également extraire et extraire les données du tableau comme je le souhaite et l'afficher dans différents formats.

Je pense donc que je ne peux pas faire de la manière Read()puis incrémenter ++ parce que cela signifie que je devrais ouvrir Read()puis ouvrir à Read()nouveau pour obtenir le nombre de lignes, puis les données de colonne.

Juste un petit exemple de ce dont je parle:

int counter = 0;    

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

puis une boucle for pour parcourir les colonnes et pop

something.Read();

int dbFields = sqlRead.FieldCount;

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

Réponses:

96

Il n'y a que deux options:

  • Découvrez en lisant toutes les lignes (et vous pouvez aussi bien les stocker)

  • exécutez au préalable une requête SELECT COUNT (*) spécialisée.

Passer deux fois dans la boucle DataReader coûte vraiment cher, vous devrez réexécuter la requête.

Et (grâce à Pete OHanlon) la deuxième option n'est sécurisée que pour la concurrence lorsque vous utilisez une transaction avec un niveau d'isolement Snapshot.

Puisque vous voulez finir par stocker toutes les lignes en mémoire de toute façon, la seule option judicieuse est de lire toutes les lignes dans un stockage flexible ( List<>ou DataTable), puis de copier les données dans le format de votre choix . L'opération en mémoire sera toujours beaucoup plus efficace.

Henk Holterman
la source
5
Henk a raison: il n'y a aucun membre du DataReader qui vous permet d'obtenir le nombre de lignes car il s'agit d'un lecteur avant uniquement. Vous feriez mieux de commencer par obtenir le décompte, puis d'exécuter la requête, peut-être dans une requête à résultats multiples afin de n'atteindre la base de données qu'une seule fois.
flipdoubt
14
Le problème avec le décompte spécialisé est qu'il est possible que le décompte soit différent du nombre de lignes renvoyées car quelqu'un d'autre a modifié les données d'une manière qui conduit au nombre de lignes renvoyées.
Pete OHanlon
1
Pete, tu as raison, cela exigerait un niveau d'isolement coûteux.
Henk Holterman
1
Merci à tous! Cela devient plus clair. Est-il donc préférable de vider toutes les informations dans le DataSet ou d'exécuter un SQL COUNT (*), de le stocker et d'exécuter la requête requise? Ou parlons-nous du nombre en cours d'exécution et du stockage de tout dans le DataSet?
Tomasz Iniewicz
4
Un RepeatableReadniveau d'isolement n'effectue pas de verrouillage de plage, il autorise donc toujours l'insertion d'enregistrements, vous devez utiliser un niveau d'isolement de Snapshotou Serializable.
Lukazoid
10

Si vous n'avez pas besoin de récupérer toute la ligne et que vous voulez éviter de faire une double requête, vous pouvez probablement essayer quelque chose comme ça:

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

Vous devrez peut-être ajouter une transaction sans savoir si la réutilisation de la même commande ajoutera automatiquement une transaction dessus ...

Pit Ming
la source
1
N'importe qui peut dire si @@ ROWCOUNT s'appuie toujours sur la dernière requête qui s'exécute ci-dessus? Des problèmes si de nombreuses connexions exécutent des requêtes en parallèle?
YvesR
1
Faut-il le faire sqlCon.Close();? J'ai pensé que ça usingdevrait le faire pour toi.
bleuté
1
cela ne fonctionnera pas au cas où nous aurions besoin de nombre de lignes avant de récupérer les données du lecteur
Heemanshu Bhalla
8

Comme ci-dessus, un ensemble de données ou un ensemble de données typé peut être une bonne structure provisoire que vous pouvez utiliser pour effectuer votre filtrage. Un SqlDataReader est destiné à lire les données très rapidement. Pendant que vous êtes dans la boucle while (), vous êtes toujours connecté à la base de données et il attend que vous fassiez tout ce que vous faites pour lire / traiter le résultat suivant avant de continuer. Dans ce cas, vous pouvez obtenir de meilleures performances si vous récupérez toutes les données, fermez la connexion à la base de données et traitez les résultats «hors ligne».

Les gens semblent détester les ensembles de données, donc ce qui précède pourrait également être fait avec une collection d'objets fortement typés.

Daniel Segan
la source
2
J'adore les DataSets moi-même, car ils sont une représentation générique bien écrite et extrêmement utile de données basées sur des tables. Curieusement, j'ai remarqué que la plupart des gens qui évitent le DataSet pour ORM sont les mêmes personnes qui essaient d'écrire leur propre code pour être aussi générique que possible (généralement inutilement).
MusiGenesis
5
Daniel, «au-dessus» n'est pas un bon moyen de référencer une autre réponse.
Henk Holterman
6

Vous ne pouvez pas obtenir un nombre de lignes directement à partir d'un lecteur de données, car il s'agit de ce que l'on appelle un curseur Firehose - ce qui signifie que les données sont lues ligne par ligne en fonction de la lecture effectuée. Je vous déconseille de faire 2 lectures sur les données car il est possible que les données aient changé entre les 2 lectures, et ainsi vous obtiendriez des résultats différents.

Ce que vous pouvez faire, c'est lire les données dans une structure temporaire et l'utiliser à la place de la deuxième lecture. Sinon, vous devrez changer le mécanisme par lequel vous récupérez les données et utilisez plutôt quelque chose comme un DataTable.

Pete OHanlon
la source
5

pour compléter la réponse Pit et pour une meilleure performance: obtenez tout en une seule requête et utilisez la méthode 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
la source
1

Je suis également confronté à une situation où je devais renvoyer un résultat supérieur, mais que je voulais également obtenir le nombre total de lignes correspondant à la requête. j'arrive enfin à cette solution:

   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
la source