104 votes

Entity Framework requête lente, mais le même SQL dans SqlQuery est rapide

Je rencontre des problèmes de performance vraiment étranges liés à une requête très simple utilisant Entity Framework Code-First avec le framework .NET version 4. La requête LINQ2Entities ressemble à ceci :

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

Cela prend plus de 3000 millisecondes pour s'exécuter. Le SQL généré semble très simple :

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Cette requête s'exécute presque instantanément lorsqu'elle est exécutée via Management Studio. Lorsque je change le code C# pour utiliser la fonction SqlQuery, elle s'exécute en 5 à 10 millisecondes :

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Ainsi, même SQL, les entités résultantes sont suivies dans les deux cas, mais il y a une grande différence de performance entre les deux. Qu'est-ce qui se passe ?

2 votes

Je suppose que vous rencontrez des retards d'initialisation - probablement la compilation de la vue. Voir MSDN: Considérations de performance pour Entity Framework 5

0 votes

J'ai essayé de pré-générer les vues, et cela ne semble pas aider. De plus, j'ai exécuté une autre requête EF avant la lente pour exclure les trucs d'initialisation. La nouvelle requête s'est exécutée rapidement, la lente s'est encore exécutée lentement, même si la préparation du contexte s'est déroulée lors de la première requête.

1 votes

@marc_s - Non, SqlQuery renverra une instance d'entité entièrement matérialisée et suivie des changements. Voir msdn.microsoft.com/en-us/library/…

107voto

Brian Sullivan Points 6392

Je l'ai trouvé. Il s'avère que c'est un problème de types de données SQL. La colonne SomeStringProp dans la base de données était un varchar, mais EF suppose que les types de chaînes .NET sont des nvarchars. Le processus de traduction résultant lors de la requête pour que la base de données effectue la comparaison est ce qui prend du temps. Je pense qu'EF Prof me trompait un peu ici, une représentation plus précise de la requête en cours serait la suivante:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Donc, la correction résultante est d'annoter le modèle code-first, indiquant le bon type de données SQL:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}

1 votes

Belle enquête. Votre requête souffrait de "conversion implicite", comme expliqué ici: brentozar.com/archive/2012/07/…

0 votes

M'a sauvé quelques heures de débogage. C'était exactement le problème.

1 votes

Dans mon cas, j'utilise EDMX avec une base de données héritée, qui utilise varchar pour tout, et en effet c'était le problème. Je me demande si je peux faire en sorte qu'un EDMX considère varchar pour chaque colonne de type chaîne de caractères.

63voto

cryss Points 974

La raison de ralentir mes requêtes EF était de comparer des scalaires non nuls avec des scalaires nuls :

long? userId = 10; // scalaire nullable

db.Table().Where(x => x.User.Id == userId).ToList() // ou userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Cette requête a pris 35 secondes. Mais une petite refacturation comme suit :

long? userId = 10;
long userIdValue = userId.Value;

db.Table().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

donne des résultats incroyables : il n'a fallu que 50 ms pour terminer. Il semble y avoir un bug dans EF.

14 votes

C'est tellement bizarre

1 votes

OMG. Cela peut apparemment également se produire lors de l'utilisation des interfaces IUserId.Id causait le problème avec moi, mais en premier lieu, le mappage Id à un entier fonctionne... dois-je maintenant vérifier toutes les requêtes dans mon application de 100 000 lignes ?

0 votes

Le bug a-t-il été signalé? Il est toujours présent dans la dernière version 6.2.0.

10voto

Matt Points 120

Si vous utilisez le mappage fluide, vous pouvez utiliser IsUnicode(false) comme partie de la configuration pour obtenir le même effet -

http://msdn.microsoft.com/fr-fr/data/jj591617.aspx#1.9

http://msdn.microsoft.com/fr-fr/library/gg696416%28v=vs.103%29.aspx

4voto

J'avais le même problème (la requête est rapide lorsqu'elle est exécutée depuis le gestionnaire SQL) mais lorsque exécutée depuis EF le délai d'expiration est dépassé.

Il s'avère que l'entité (qui a été créée à partir de la vue) avait des clés d'entité incorrectes. Ainsi, l'entité avait des lignes en doublon avec les mêmes clés, et je suppose qu'il devait regrouper en arrière-plan.

4voto

Oskar Sjöberg Points 301

J'ai également rencontré ce problème. Il s'avère que le coupable dans mon cas était le parameter sniffing de SQL-Server.

Le premier indice que mon problème était en fait dû au parameter sniffing était que l'exécution de la requête avec "set arithabort off" ou "set arithabort on" donnait des temps d'exécution drastiquement différents dans Management Studio. Cela est dû au fait qu'ADO.NET utilise par défaut "set arithabort off" et que Management Studio utilise par défaut "set arithabort on". Le cache du plan de requête conserve différents plans en fonction de ce paramètre.

J'ai désactivé le cache du plan de requête pour la requête, avec la solution que vous pouvez trouver ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X