À l'aide de Contains
dans l'Entité Cadre est effectivement très lent. C'est vrai que ça se traduit par un IN
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 développée en une longue chaîne de l' OR
concaténations car il n'est pas natif de l'expression qui représente un IN
. Lorsque le SQL est créée cette expression de nombreux OR
s est reconnu et s'effondra en arrière dans le SQL IN
de la clause.
Cela ne signifie pas que l'utilisation d' Contains
est pire que la délivrance d'une requête par l'élément dans votre ids
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 un Contains
de la 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)
ou dbContext.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:
using (var context = new MyDbContext())
{
Random rand = new Random();
var ids = new List<int>();
for (int i = 0; i < 20000; i++)
ids.Add(rand.Next(550000));
Stopwatch watch = new Stopwatch();
watch.Start();
// here are the code snippets from below
watch.Stop();
var msec = watch.ElapsedMilliseconds;
}
Test 1
var result = context.Set<MyEntity>()
.Where(e => ids.Contains(e.ID))
.ToList();
Résultat -> ms = 85.5 sec
Test 2
var result = context.Set<MyEntity>().AsNoTracking()
.Where(e => ids.Contains(e.ID))
.ToList();
Résultat -> ms = 84.5 sec
Ce petit effet de l' 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
var values = new StringBuilder();
values.AppendFormat("{0}", ids[0]);
for (int i = 1; i < ids.Count; i++)
values.AppendFormat(", {0}", ids[i]);
var sql = string.Format(
"SELECT * FROM [MyDb].[dbo].[MyEntities] WHERE [ID] IN ({0})",
values);
var result = context.Set<MyEntity>().SqlQuery(sql).ToList();
Résultat -> ms = 5.1 sec
Test 4
// same as Test 3 but this time including AsNoTracking
var result = context.Set<MyEntity>().SqlQuery(sql).AsNoTracking().ToList();
Résultat -> ms = 3.8 sec
Cette fois, l'effet de la désactivation est plus perceptible.
Test 5
// same as Test 3 but this time using Database.SqlQuery
var result = context.Database.SqlQuery<MyEntity>(sql).ToList();
Résultat -> ms = 3,7 sec
Ma compréhension est qu' context.Database.SqlQuery<MyEntity>(sql)
est le même que context.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
:
var result = new List<MyEntity>();
foreach (var id in ids)
result.Add(context.Set<MyEntity>().SingleOrDefault(e => e.ID == id));
Résultat -> ms = 73.6 sec
Notez que j'ai utilisé SingleOrDefault
au lieu de Find
. En utilisant le même code avec Find
est très lent (j'ai annulé le test au bout de plusieurs minutes), car Find
des appels DetectChanges
en interne. La désactivation automatique de la détection de changement (context.Configuration.AutoDetectChangesEnabled = false
) conduit à peu près les mêmes performances que SingleOrDefault
. À l'aide de AsNoTracking
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.