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

93

Je vois une performance vraiment étrange liée à une requête très simple utilisant Entity Framework Code-First avec .NET Framework version 4. La requête LINQ2Entities ressemble à ceci:

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

Cela prend plus de 3000 millisecondes à 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, il s'exécute dans 5 à 10 millisecondes:

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

Donc, exactement le même SQL, les entités résultantes sont suivies des modifications dans les deux cas, mais une différence de performance sauvage entre les deux. Ce qui donne?

Brian Sullivan
la source
2
Je suppose que vous voyez des délais d'initialisation - probablement voir la compilation. Voir MSDN:Performance Considerations for Entity Framework 5
Nicholas Butler
J'ai essayé des vues pré-générées, et cela ne semble pas aider. En outre, a exécuté une autre requête EF avant la lente pour exclure les éléments d'initialisation. La nouvelle requête s'est exécutée rapidement, la plus lente fonctionnait toujours lentement, même si le préchauffage du contexte s'est produit pendant la première requête.
Brian Sullivan
1
@marc_s - Non, SqlQuery renverra une instance d'entité entièrement matérialisée et suivie des modifications. Voir msdn.microsoft.com/en-us/library/…
Brian Sullivan
Le SQL généré pour votre requête EF inclut-il réellement la valeur du paramètre ou utilise-t-il un paramètre? Cela ne devrait pas affecter la vitesse de requête pour une requête individuelle, mais pourrait entraîner un gonflement du plan de requête sur le serveur au fil du temps.
Jim Wooley
Avez-vous essayé d'exécuter la même requête deux / plusieurs fois? Combien de temps a-t-il fallu lors de la deuxième exécution? Avez-vous essayé cela sur .NET Framework 4.5 - il existe des améliorations de performances liées à EF dans .NET Framework 4.5 qui pourraient vous aider.
Pawel

Réponses:

96

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

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

Le correctif résultant consiste donc à annoter le modèle code-first, en indiquant le type de données SQL correct:

public class MyTable
{
    ...

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

    ...
}
Brian Sullivan
la source
1
Belle enquête. Votre requête souffrait d'une "conversion implicite", comme expliqué ici: brentozar.com/archive/2012/07
Jaime
M'a sauvé quelques heures de débogage. C'était exactement le problème.
Cody
1
Dans mon cas, j'utilise EDMX avec une base de données héritée, qui utilise varcharpour tout, et c'était effectivement le problème. Je me demande si je peux créer un EDMX pour considérer varchar pour tout ce qui concerne la colonne de chaîne.
Alisson
1
Super homme de recherche. mais @Jaime ce que nous devrions faire pour la base de données approche d'abord car tout (par exemple l'annotation de données sur les modèles db) s'efface après la mise à jour du modèle EF à partir de la base de données.
Nauman Khan
Définir ceci comme ma page d'accueil pendant un certain temps afin que je puisse revivre l'excitation de trouver à nouveau une si bonne réponse pendant un moment. Je vous remercie!!!
OJisBad
43

La raison du ralentissement de mes requêtes effectuées dans EF était de comparer des scalaires non nullables avec des scalaires nullables:

long? userId = 10; // nullable scalar

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

Cette requête a pris 35 secondes. Mais un petit refactoring comme ça:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().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 est possible que ce soit un bogue dans EF.

cryss
la source
13
C'est tellement bizarre
Daniel Cardenas
1
OMG. Cela peut apparemment également se produire lors de l'utilisation des interfaces IUserId.Id me causait le problème, mais le premier mappage de l'ID à un entier fonctionne ... Dois-je vérifier maintenant toutes les requêtes dans mon application de 100 000 lignes?
Dirk Boer
ce bogue a-t-il été signalé? Il est toujours dans la dernière version 6.2.0
Dirk Boer
2
Le même problème est également dans EF Core. Merci d'avoir trouvé ça!
Yannickv le
Une autre suggestion consiste à traiter la variable avant de la mettre dans l'expression LINQ. Sinon, le SQL généré sera beaucoup plus long et plus lent. J'ai vécu avec Trim () et ToLower () dans l'expression LINQ qui me dérange.
samheihey le
4

J'ai eu le même problème (la requête est rapide lorsqu'elle est exécutée à partir du gestionnaire SQL) mais lorsqu'elle est exécutée à partir d'EF, le délai expire.

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

Vladimir Gedgafov
la source
3

Je suis également tombé sur cela avec une requête ef complexe. Un correctif pour moi qui a réduit une requête ef de 6 secondes à la sous-deuxième requête SQL qu'elle a générée était de désactiver le chargement différé.

Pour trouver ce paramètre (ef 6), allez dans le fichier .edmx et regardez dans Propriétés -> Génération de code -> Chargement différé activé. Défini sur false.

Amélioration massive des performances pour moi.

user2622095
la source
4
C'est cool, mais cela n'a rien à voir avec la question des affiches.
Jace Rhea
2

J'ai eu ce problème aussi. Il s'avère que le coupable dans mon cas était le reniflement de paramètres SQL-Server .

Le premier indice que mon problème était en fait dû au reniflage de paramètres était que l'exécution de la requête avec «set arithabort off» ou «set arithabort on» donnait des temps d'exécution radicalement 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é la mise en cache du plan de requête pour la requête, avec la solution que vous pouvez trouver ici .

Oskar Sjöberg
la source