2017-05-04 3 views
0

J'ai une API en C# qui retourne des données d'un DB et un frontend qui peint ces données dans une table.C# Write Async une taille de données spécifique

Mon approche consistait à lire les données de la base de données avec un sqlReader, itérer à travers ce lecteur en ajoutant chaque résultat à une liste et renvoyer cette liste au frontend.

Semble assez facile, jusqu'à ce que je reçoive des données de requête massives. Ma solution était de retourner ce bloc de données par morceau, mais je suis coincé avec elle, c'est le code que je travaille avec:

var sqlCommand = db.InitializeSqlCommand(query); 
       try 
       { 
        using (var reader = sqlCommand.ExecuteReader()) 
        { 
         var results = new List<List<string>>(); 
         var headers = new List<string>(); 
         var rows = new List<string>(); 
         for (var i = 0; i < reader.FieldCount; i++) 
         { 
          headers.Add(reader.GetName(i)); 
         } 
         results.Add(headers); 
         while (reader.Read()) 
         { 
          for (var i = 0; i < reader.FieldCount; i++) 
          { 
           rows.Add((reader[reader.GetName(i)]).ToString()); 
          } 
          results.Add(rows); 
          var str = JsonConvert.SerializeObject(results); 
          var buffer = Encoding.UTF8.GetBytes(str); 

          //Thread.Sleep(1000); 
          await outputStream.WriteAsync(buffer, 0, buffer.Length); 
          rows.Clear(); 
          results.Clear(); 
          outputStream.Flush(); 

         } 
        } 
       } 
       catch (HttpException ex) 
       { 
        if (ex.ErrorCode == -2147023667) // The remote host closed the connection. 
        { 
        } 
       } 
       finally 
       { 
        outputStream.Close(); 
        db.Dispose(); 
       } 

Avec cela, je suis en mesure de renvoyer des données un par un (testé avec le Thread.sleep), mais je suis coincé sur la façon de retourner un montant spécifique, disons 200 données ou 1000, cela ne devrait vraiment pas avoir d'importance.

Une idée sur la façon de procéder? Merci d'avance. Mese.

+0

Comment utiliser 'OFFSET' et' FETCH NEXT' dans votre commande SQL? Vous pouvez augmenter le 'OFFSET' pour chaque exécution suivante. Exemple - après la clause ORDER BY ajouter 'OFFSET 200 ROWS FETCH NEXT 200 ROWS SEULEMENT' pour ignorer 200 lignes et obtenir les 200 prochaines – degant

+0

Ce n'est pas une possibilité, c'est une requête dynamique, je ne suis pas censé savoir quelle requête arrive. C'est pourquoi je veux que tout soit dynamique – Mese

+0

Donc vous n'avez pas de contrôle sur la requête. Ensuite, le seul moyen est de contrôler le nombre de lignes que le lecteur lit. Que diriez-vous de 'foreach (var row dans reader.AsEnumerable.Skip (200).Prenez (200)) {// opération} 'et vous pouvez modifier le 200 dans les appels suivants – degant

Répondre

2

Je pense que le contrôle de la requête est le meilleur moyen puisque c'est ce qui sera extrait de la base de données. Vous pouvez augmenter le OFFSET pour chaque série suivante. Exemple - après la clause ORDER BY ajouter OFFSET 200 ROWS FETCH NEXT 200 ROWS ONLY pour ignorer 200 lignes et obtenir les 200 prochaines.

Cependant, puisque vous avez mentionné que vous n'avez aucun contrôle sur la requête, vous pouvez faire quelque chose comme ceci pour filtrer nos résultats de votre côté . L'astuce consiste ici à utiliser reader.AsEnumerable.Skip(200).Take(200) pour choisir les lignes à traiter. Mettez à jour l'entrée à Skip() à chaque itération pour traiter les données en conséquence.

// Offset variable will decide how many rows to skip, the outer while loop can be 
// used to determine if more data is present and increment offset by 200 or any 
// other value as required. Offset -> 0, 200, 400, 600, etc.. until data is present 

bool hasMoreData = true; 
int offset = 0; 
while(hasMoreData) 
{ 
    // SQL Data reader and other important operations 
    foreach(var row in reader.AsEnumerable.Skip(offset).Take(200)) 
    { 
     // Processing operations 
    } 
    // Check to ensure there are more rows 
    if(no more rows) 
     hasMoreData = false; 
    offset += 200; 
} 

Une autre chose à garder à l'esprit est lorsque vous tirez les données par lots, la requête exécutera plusieurs fois et si pendant ce temps, un nouveau record a obtenu ajouté ou supprimé, les lots ne fonctionnent pas correctement. Pour passer, vous pouvez faire 2 choses:

  • Valider un ID unique de chaque enregistrement avec ID uniques des enregistrements déjà récupérés pour vous assurer que le même enregistrement est pas tiré deux fois (cas de base en raison d'enregistrer plus/suppression)
  • Ajouter un tampon pour le décalage, par exemple
    • Skip(0).Take(100) // Poignées 0 - 100 records
    • Skip(90).Take(100)
    • // Poignées 90 - 190 résultats (chevauchement de 10 pour répondre à des additions/deletions)
    • Skip(180).Take(100) // 180 Poignées - 280 dossiers (recouvrement de 10 pour répondre à des ajouts/suppressions)
    • et ainsi de suite ...

Hope this helps!

+0

Pour ceux qui sont intéressés, ce code fonctionne un peu pour moi, la seule différence est que j'ai besoin d'un moyen dynamique pour augmenter la quantité de saut, donc j'ai juste ajouté une variable et augmenter sa taille à chaque boucle. Merci @degant – Mese