2010-06-10 3 views
2

Je me mouille les pieds avec Linq. J'essaie de déterminer les valeurs distinctes contenues dans quatre DataColumns. Donc, je commence parAccéder au résultat Linq vide est très lent

var c1types = (from DataRow row in dtSource.Select("hasreq") 
       where row["m"].ToInt() > 0 
       select new { col = row["m"] }).Distinct(); 
var c2types = (from DataRow row in dtSource.Select("hasreq") 
       where row["w"].ToInt() > 0 
       select new { col = row["w"] }).Distinct(); 
var c3types = (from DataRow row in dtSource.Select("hasreq") 
       where row["ag"].ToInt() > 0 
       select new { col = row["ag"] }).Distinct(); 
var c4types = (from DataRow row in dtSource.Select("hasreq") 
       where row["aq"].ToInt() > 0 
       select new { col = row["aq"] }).Distinct(); 

foreach (var type in c1types.Union(c2types).Union(c3types).Union(c4types).Distinct()) 
{ 
    ... 
} 

Cela fonctionne, mais est très lent (4-5 secondes). Donc, je mets ce qui suit avant le foreach

MessageBox.Show(c1types.Count().ToString()); // 1 - immediate display 
MessageBox.Show(c2types.Count().ToString()); // 1 - immediate display 
MessageBox.Show(c3types.Count().ToString()); // 1 - immediate display 
MessageBox.Show(c4types.Count().ToString()); // 0 - 4-5 seconds to display 

Avec mes données d'échantillons, chacun des trois premiers choisit retourne une seule valeur distincte (count() == 1 de). La quatrième renvoie aucune valeur (Count() == 0). Ce que je ne comprends pas, c'est pourquoi il affiche instantanément les trois premiers chiffres, mais le quatrième prend 4-5 secondes à afficher. Il semblerait que le résultat vide soit la cause du ralentissement. Qu'est-ce qui se passe ici, et quelle est la meilleure solution de contournement?

+0

utilisez-vous .net 3.5 ou .net 4.0? – Luke101

+0

J'utilise 3.5. – PahJoker

+0

Je n'ai pas beaucoup d'expérience DataTable - est-ce que les requêtes touchent chacune un backend de base de données ou s'agit-il simplement de requêtes en mémoire? Si une base de données, en vérifiant le SQL (sql profiler, linqpad, peu importe) pourrait aider à comprendre ce qui se passe (index manquant? Mauvaise requête? Etc) –

Répondre

1

De votre code, je devine que ToInt() est une méthode d'extension. Je suis assez sûr que l'indexeur sur DataRow renvoie des objets, et je ne me souviens pas de l'objet définissant ToInt().

Si cela est vrai, ToInt peut être en train de faire des fautes de performances. Peut-être qu'il fait quelque chose comme

try { return Int32.Parse(arg); } 
catch { return 0; } 

Si Int32.Parse ne peut pas gérer la plupart des valeurs de la ligne [ « aq »], il pourrait être la cause de la lenteur - vérifiez votre fenêtre de débogage pour les exceptions. Si c'est le problème, vous pouvez l'accélérer en utilisant Int32.TryParse qui ne lèvera pas une exception.

Si je me trompe, pouvez-vous fournir plus d'informations? Qu'est-ce que ToInt()?

EDIT:

je aurais dû savoir qu'il serait Convert.ToInt32, comme Int32.Parse prend une chaîne pour un paramètre. Ma solution proposée est la suivante.

La critique de l'utilisation de Convert.ToInt32:

Vous devriez pas exceptions de capture de Convert.ToInt32. Il jette ce que Eric Lippert appellerait des exceptions 'Boneheaded'. Les exceptions qui devraient jamais arriver. Si vous obtenez une exception de Convert.ToInt32, votre programme est erroné. Vous avez essayé de convertir quelque chose en un Int32 qui ne représente pas un Int32. Considérez à quoi ressembleront les tests unitaires pour la méthode d'extension ToInt. Vous pouvez appeler myPrizeSheep.ToInt() et obtenir 0. La conversion d'un mouton en nombre est-elle logique? Avaler des exceptions de Convert.Int32 entraînera généralement des problèmes à long terme - dans votre cas, il s'agit d'un problème de performances, mais cela peut souvent être pire - un problème de correction.

Il n'existe pas de Convert.TryConvertInt32 (Object). Il y a un Int32.TryParse (String) cependant. C'est parce qu'il est très commun de vouloir analyser une entrée de chaîne par un utilisateur à un Int32. Vous vous attendez à ce qu'ils aient entré quelque chose qui n'est pas un Int32 - ce n'est pas un cas exceptionnel s'ils le font - vous pouvez juste dire à l'utilisateur de le corriger - cela fait partie du flux normal d'exécution du programme.

Si vous avez un objet, doit savoir qu'il représente un Int32 afin de vouloir essayer de le convertir. Si ce que vous passez à Convert.ToInt32 ne représente pas un Int32, alors c'est un cas exceptionnel. Je ne peux pas penser à une seule instance où vous voudriez 'essayer' de convertir n'importe quel ancien Object en un Int32 - et évidemment, les développeurs de BCL ne le peuvent pas non plus.

Je ne pense pas que ToInt soit un bon usage d'une méthode d'extension. J'utilise généralement des méthodes d'extension afin de pouvoir enchaîner des appels en utilisant une belle syntaxe de style pipe-forward. Il existe peu d'exemples dans lesquels vous souhaitez chaîner ToInt() à d'autres appels de méthode. Parce que tout ToInt fait s'appelle Convert.ToInt32 et avale faussement toutes les exceptions. Il vaut mieux utiliser simplement Convert.ToInt32 dans vos exemples.

La solution:

Tenir compte comment faire face à des choses qui ne sont pas ints dans votre requête Linq. Dans votre cas, ils sont susceptibles d'être NULL ou DBNulls. Il est probable que vous voulez exclure les lignes, dans ce cas, vous pouvez écrire quelque chose le long des lignes de:

var c4types = (from DataRow row in dtSource.Select("hasreq") 
       where row["aq"] != null && row["aq"] != DBNull.Value && row["aq"].ToInt() > 0 
       select new { col = row["aq"] }).Distinct(); 

Cette expression présente l'inconvénient que vous vous retrouvez avec une liste d'objets plutôt que INT32. Vous pourriez faire une conversion dans la sélection finale, mais vous feriez ensuite la conversion deux fois. Pour être honnête, ma façon préférée (si vous voulez une collection de INT32 plutôt que des objets) serait:

var c4types = dtSource.Select("hasreq") 
         .Where(row => row["aq"] != null && row["aq"] != DBNull.Value) 
         .Select(Convert.ToInt32(row["aq"]) 
         .Where(i => i > 0) 
         .Distinct(); 

Dans ce qui précède, nous avons d'abord se débarrasser de lignes avec des valeurs nulles. Nous convertissons ensuite les lignes que nous connaissons. Puis nous nous débarrassons des ints de moins de 1, et obtenons finalement une collection unique d'entiers.

+0

Vous avez parfaitement raison que c'est une méthode d'extension, et est similaire à votre code, mais utilise Convert.ToInt32(). Je vais essayer votre suggestion quand j'aurai une chance et posterai mes conclusions à ce moment-là. Merci! – PahJoker

+0

@pahjoker - J'aurais dû savoir que c'était Convert.ToInt32! Voir mes modifications ci-dessus - ne prenez pas ce que j'ai dit personnellement, je suis juste en train d'essayer de vous aider à éviter les problèmes futurs qui pourraient survenir si vous continuez en utilisant cette méthode d'extension ToInt :) –