Pourquoi est .Contient-il lent? Le moyen le plus efficace pour obtenir plusieurs entités à l'aide de la clé primaire?
Quel est le moyen le plus efficace pour sélectionner plusieurs entités à l'aide de la clé primaire?
public IEnumerable<Models.Image> GetImagesById(IEnumerable<int> ids)
{
//return ids.Select(id => Images.Find(id)); //is this cool?
return Images.Where( im => ids.Contains(im.Id)); //is this better, worse or the same?
//is there a (better) third way?
}
Je me rends compte que je pouvais faire des tests de performance pour comparer, mais je me demande si il n'y a en fait une meilleure façon que les deux autres, et suis à la recherche de quelques lumières sur ce qu'est la différence entre ces deux requêtes est, le cas échéant, une fois qu'ils ont été "traduits".
- Eh bien, c'est de durée indéterminée, généralement pas un grand nombre, mais voudrait être en mesure de soutenir un grand nombre évolutive de la mode.
- J'ai pris la liberté de re-délivrance de titres de propriété à votre question de le rendre plus convivial pour les personnes en recherche plus rapide d'un remplacement pour
.Contains
. - Remarque ce problème est résolu en EF 6 - voir le 5e point - blogs.msdn.com/b/adonet/archive/2012/12/10/...
- L'homme, pourquoi dois-je reviens à cette question...?
- Voir aussi: Évolutive Contient méthode pour LINQ contre un backend SQL.
Vous devez vous connecter pour publier un commentaire.
Mise à JOUR: Avec l'ajout d'InExpression dans EF6, les performances du traitement de Énumérable.Contient amélioré de façon spectaculaire. L'analyse de cette réponse, c'est grand, mais en grande partie obsolète depuis 2013.
À l'aide de
Contains
dans l'Entity Framework est effectivement très lent. C'est vrai que ça se traduit par uneIN
clause SQL et que la requête SQL lui-même est exécuté rapidement. Mais le problème et le goulot d'étranglement des performances est à la traduction à partir de votre requête LINQ en SQL. L'expression de l'arbre qui sera créé est élargi en une longue chaîne deOR
concaténations car il n'est pas natif de l'expression qui représente uneIN
. Lorsque le SQL est créée cette expression de nombreuxOR
s est reconnu et s'effondra en arrière dans le SQLIN
clause.Cela ne signifie pas que l'utilisation de
Contains
est pire que la délivrance d'une requête par l'élément dans votreids
collection (votre première option). C'est probablement encore mieux - au moins pour ne pas trop grandes collections. Mais pour les grandes collections il est vraiment mauvais. Je me souviens que j'avais testé il y a quelques temps unContains
requête avec près de 12.000 éléments qui ont travaillé, mais a pris environ une minute, même si la requête en SQL exécuté en moins d'une seconde.Il pourrait être intéressant de tester les performances de la combinaison de plusieurs allers-retours vers la base de données avec un plus petit nombre d'éléments dans un
Contains
expression pour chaque aller-retour.Cette approche et les limites de l'utilisation de
Contains
avec Entity Framework est montré et expliqué ici:Pourquoi ne le Contient() l'opérateur de se dégrader Entité du Cadre de performance de façon spectaculaire?
Il est possible qu'un raw commande SQL donnent de meilleurs résultats dans cette situation, ce qui signifie que vous appelez
dbContext.Database.SqlQuery<Image>(sqlString)
oudbContext.Images.SqlQuery(sqlString)
oùsqlString
est le SQL montré dans @Rune de réponse.Modifier
Voici quelques mesures:
Je l'ai fait sur une table avec 550000 dossiers et 11 colonnes (Id 1 sans lacunes) et choisi au hasard 20000 id:
Test 1
Result -> msec = 85.5 sec
Test 2
Result -> msec = 84.5 sec
Ce petit effet de
AsNoTracking
est très inhabituel. Il indique que le goulot d'étranglement est pas l'objet de la réalisation (et non SQL, comme le montre ci-dessous).Pour les deux tests, il peut être vu dans le générateur de profils SQL le SQL de la requête arrive à la base de données à très tard. (Je n'ai pas de mesurer exactement, mais il était plus tard que 70 secondes.) Évidemment, la traduction de cette requête LINQ en SQL est très cher.
Test 3
Result -> msec = 5.1 sec
Test 4
Result -> msec = 3.8 sec
Ce temps, l'effet de la désactivation est plus perceptible.
Test 5
Result -> msec = 3,7 sec
Ma compréhension est que
context.Database.SqlQuery<MyEntity>(sql)
est le même quecontext.Set<MyEntity>().SqlQuery(sql).AsNoTracking()
, donc il n'y a pas de différence attendue entre 4 et Test 5.(La longueur des ensembles de résultats n'est pas toujours la même en raison des doublons possibles après l'aléatoire de la sélection d'id, mais il était toujours entre 19600 et 19640 éléments.)
Edit 2
Test 6
Même 20000 allers-retours vers la base de données sont plus rapide que d'utiliser
Contains
:Result -> msec = 73.6 sec
Noter que j'ai utilisé
SingleOrDefault
au lieu deFind
. En utilisant le même code avecFind
est très lent (j'ai annulé le test au bout de plusieurs minutes) parce queFind
appelsDetectChanges
en interne. La désactivation automatique de la détection de changement (context.Configuration.AutoDetectChangesEnabled = false
) conduit à peu près les mêmes performances queSingleOrDefault
. À l'aide deAsNoTracking
réduit le temps d'une ou deux secondes.Des Tests ont été réalisés avec le client de base de données (application console) et le serveur de base de données sur la même machine. Le dernier résultat peut obtenir beaucoup plus de mal avec une "distance" de la base de données en raison des nombreux allers-retours.
La deuxième option est nettement mieux que le premier. La première option est
ids.Length
requêtes à la base de données, tandis que la deuxième option peut utiliser un'IN'
opérateur dans la requête SQL. Il va transformer votre requête LINQ en quelque chose comme le SQL suivant:où valeur1, valeur2, etc. sont les valeurs de votre id de la variable. Soyez conscient, cependant, que je pense qu'il y a peut être une limite supérieure sur le nombre de valeurs qui peuvent être sérialisés dans une requête dans ce sens. Je vais voir si je peux trouver de la documentation...
column IN (val1, val2, val3, ...)
) alors que LINQ-to-SQL paramètre toutes les valeurs (column IN (@p1, @p2, @p3, ...)
) et donc, vous a frappé le 2100 paramètre limite assez rapidement.Je suis en utilisant Entity Framework 6.1 et a découvert à l'aide de votre code que, il est préférable d'utiliser:
plutôt que:
La Performance de Find() vs FirstOrDefault sont quelques idées sur ce point.
Weel, récemment, ont un problème similaire et la meilleure façon que j'ai trouvée a été d'insérer la liste de contient dans une Table temporaire, et après faire une jointure.
Ce n'est pas une jolie façon, mais pour les grandes listes, il est très performant.
La transformation de la Liste à un Tableau avec toArray() augmente les performances. Vous pouvez le faire de cette façon:
Images.toArray()
tire le toutImages
table en mémoire. Vous ne pouvez pas sérieusement dire que. Aussi, il n'est pas clair ce que la première ligne est en train de faire là.