Entity Framework est trop lent. Quelles sont mes options? [fermé]

93

J'ai suivi le mantra «Ne pas optimiser prématurément» et codé mon service WCF en utilisant Entity Framework.

Cependant, j'ai profilé les performances et Entity Framework est trop lent. (Mon application traite 2 messages en 1,2 seconde environ, alors que l'application (héritée) que je réécrit fait 5 à 6 messages en même temps (l'application héritée appelle sprocs pour son accès à la base de données).)

Mon profilage pointe vers Entity Framework prenant la majeure partie du temps par message.

Alors, quelles sont mes options?

  • Existe-t-il de meilleurs ORM?
    (Quelque chose qui prend simplement en charge la lecture et l'écriture normales des objets et le fait rapidement.)

  • Existe-t-il un moyen de rendre Entity Framework plus rapide?
    ( Remarque : quand je dis plus vite, je veux dire sur le long terme, pas le premier appel. (Le premier appel est lent (15 secondes pour un message), mais ce n'est pas un problème. J'ai juste besoin que ce soit rapide pour le reste. des messages.)

  • Une troisième option mystérieuse qui m'aidera à obtenir plus de vitesse de mon service.

REMARQUE: la plupart de mes interactions DB sont Créer et Mettre à jour. Je fais très très peu de sélection et de suppression.

Vaccano
la source
Cela ressemble à une répétition de «linq est lent» comment savez-vous que c'est EF? Avez-vous profilé tous vos changements?
Maess
6
Certaines des réponses pointent vers les requêtes. D'après mon expérience, la lenteur d'EF a peu à voir avec les requêtes, mais plutôt avec les coûts de matérialisation, et ces coûts sont souvent liés au suivi des modifications et à la façon dont cela affecte les instances créées. Malheureusement, je n'ai pas de solution miracle pour vous, ce n'est donc qu'un commentaire, mais je recommanderais de voir si le profilage révèle des coûts de matérialisation élevés et, si oui, de rechercher ce qui peut être fait à propos de ces coûts.
Anthony Pegram
@Maess - Je pensais avoir indiqué que j'avais profilé et trouvé que c'était EF / DB qui était lent. De toute façon, oui je l'ai fait. Je l'ai profilé et ce sont les interactions EF / DB qui sont le principal coupable.
Vaccano
@Anthony - La matérialisation n'est-elle pas d'abord un genre de choses? Si c'est le cas, vous avez raison de dire que c'est très lent. La première manche est super lente. Mais comme je l'ai indiqué, je ne suis pas trop inquiet à ce sujet. C'est le débit total qui pose problème. (Si ce n'est pas ce qu'est la matérialisation, alors je dois faire quelques recherches pour voir si c'est la cause de mon problème)
Vaccano
1
@Vaccano, non, la matérialisation est le processus qui consiste à extraire les données de la base de données et à instancier et à peupler le graphique des objets pour représenter ces données. Je ne parle pas des performances de première exécution car le code est jit (ou même du fait que Sql Server peut créer le plan d'exécution de la requête), mais de ce qui se passe à chaque fois que vous obtenez des données sous forme d'objets.
Anthony Pegram

Réponses:

46

Vous devez commencer par profiler les commandes SQL réellement émises par Entity Framework. En fonction de votre configuration (POCO, entités Self-Tracking), il y a beaucoup de place pour les optimisations. Vous pouvez déboguer les commandes SQL (qui ne devraient pas différer entre les modes de débogage et de version) à l'aide de la ObjectSet<T>.ToTraceString()méthode. Si vous rencontrez une requête qui nécessite une optimisation supplémentaire, vous pouvez utiliser certaines projections pour donner à EF plus d'informations sur ce que vous essayez d'accomplir.

Exemple:

Product product = db.Products.SingleOrDefault(p => p.Id == 10);
// executes SELECT * FROM Products WHERE Id = 10

ProductDto dto = new ProductDto();
foreach (Category category in product.Categories)
// executes SELECT * FROM Categories WHERE ProductId = 10
{
    dto.Categories.Add(new CategoryDto { Name = category.Name });
}

Peut être remplacé par:

var query = from p in db.Products
            where p.Id == 10
            select new
            {
                p.Name,
                Categories = from c in p.Categories select c.Name
            };
ProductDto dto = new ProductDto();
foreach (var categoryName in query.Single().Categories)
// Executes SELECT p.Id, c.Name FROM Products as p, Categories as c WHERE p.Id = 10 AND p.Id = c.ProductId
{
    dto.Categories.Add(new CategoryDto { Name = categoryName });
}

Je viens de taper cela de ma tête, donc ce n'est pas exactement comment cela serait exécuté, mais EF fait en fait de belles optimisations si vous lui dites tout ce que vous savez sur la requête (dans ce cas, nous aurons besoin de la catégorie- des noms). Mais ce n'est pas comme un chargement hâtif (db.Products.Include ("Categories")) car les projections peuvent réduire davantage la quantité de données à charger.

J. Tihon
la source
40
Cette réponse semble raisonnable, jusqu'à ce que vous vous rendiez compte que les types anonymes ne sont pas accessibles en dehors de la méthode dans laquelle ils sont définis. Si vous voulez charger un objet complexe et ne pas écrire un mégamoth, vous devez alors désérialiser vos nouveaux types anonymes en une sorte de POCO. Encore une fois, cela semble presque raisonnable jusqu'à ce que vous vous rendiez compte que, ce faisant, vous avez essentiellement RÉÉCRIT VOTRE PROPRE CADRE D'ENTITÉ. Ce qui est des conneries.
Doug
5
cela a entraîné une augmentation de vitesse de 15x à 20x pour moi.
Dave Cousineau
11
Réponse intéressante et utile, encore valable un certain temps plus tard. @Doug: Ce qui n'est pas vraiment une connerie puisque vous n'optimisez (en utilisant des projections) que les quelques requêtes où vous avez vraiment besoin d'utiliser l'avantage supplémentaire. EF et POCO vous donnent un défaut raisonnable, ce qui est très agréable!
Victor
2
@Doug La plupart des applications ont des modèles de vue pour des scénarios en lecture seule, n'est-ce pas? Autant faire autant de mappage que d'extraire les données.
Casey
4
J'avais l'habitude de sentir que les ORM étaient l'avenir. Ils avaient juste du sens, jusqu'à ce que je commence à les utiliser. Puis j'ai trouvé Dapper . Maintenant, en voyant des solutions comme celle-ci, je grince des dents à la façon dont la complexité augmente rapidement. Ecrire du SQL abstrait en C # n'est pas un moyen de traverser la vie.
Michael Silver
80

Le fait est que les produits tels que Entity Framework seront TOUJOURS lents et inefficaces, car ils exécutent beaucoup plus de code.

Je trouve aussi ridicule que les gens suggèrent d'optimiser les requêtes LINQ, de regarder le SQL généré, d'utiliser des débogueurs, de pré-compiler, de prendre de nombreuses étapes supplémentaires, etc. c'est-à-dire perdre beaucoup de temps. Personne ne dit - Simplifiez! Tout le monde veut compliquer davantage les choses en prenant encore plus de mesures (perdre du temps).

Une approche de bon sens serait de ne pas utiliser du tout EF ou LINQ. Utilisez du SQL brut. Il n'y a rien de mal à cela. Ce n'est pas parce qu'il y a une mentalité de troupeau parmi les programmeurs et qu'ils ressentent le besoin d'utiliser chaque nouveau produit qu'il existe, qu'il est bon ou qu'il fonctionnera. La plupart des programmeurs pensent que s'ils incorporent chaque nouveau morceau de code publié par une grande entreprise, cela en fait un programmeur plus intelligent; pas vrai du tout. La programmation intelligente consiste principalement à faire plus avec moins de maux de tête, d'incertitudes et en un minimum de temps. Souvenez-vous du temps! C'est l'élément le plus important, alors essayez de trouver des moyens de ne pas le gaspiller en résolvant des problèmes dans un code mauvais / gonflé écrit simplement pour se conformer à certains soi-disant `` modèles '' étranges.

Détendez-vous, profitez de la vie, faites une pause dans le codage et arrêtez d'utiliser des fonctionnalités supplémentaires, du code, des produits, des «modèles». La vie est courte et la durée de vie de votre code est encore plus courte, et ce n'est certainement pas sorcier. Supprimez les couches telles que LINQ, EF et autres, et votre code fonctionnera efficacement, sera mis à l'échelle et, oui, il sera toujours facile à maintenir. Trop d'abstraction est un mauvais «modèle».

Et c'est la solution à votre problème.

Sean
la source
155
C'est jeter le bébé avec l'eau du bain. Vous optimisez les goulots d'étranglement, il est ridicule de jeter EF car il est trop lent à certains endroits, tout en étant très rapide dans la plupart des autres. Pourquoi ne pas utiliser les deux? EF gère très bien les procédures stockées et le SQL brut. Je viens de convertir une requête LINQ-to-SQL qui a pris plus de 10 secondes en un SP qui prend ~ 1 seconde, mais je ne vais pas rejeter tout LINQ-to-SQL. Cela a sauvé BEAUCOUP de temps dans d'autres cas plus simples, avec moins de code et moins de marge d'erreur et les requêtes sont vérifiées par le compilateur et correspondent à la base de données. Moins de code est une maintenance plus facile et moins de place pour les erreurs.
JulianR
11
Dans l'ensemble, vos conseils sont bons, mais je ne pense pas qu'il soit juste d'abandonner EF ou d'autres abstractions car ils ne fonctionnent pas bien 10% du temps.
JulianR
49
Plain SQL = facile à entretenir? Ce n'est tout simplement pas le cas pour les très grandes applications avec beaucoup de logique métier. Écrire du SQL complexe réutilisable n'est pas une chose facile à faire. Personnellement, j'ai eu des problèmes de performances avec EF, mais ces problèmes ne se comparent tout simplement pas aux avantages d'un ORM approprié en termes de RAD et de garder les choses SEC (s'il y a un niveau de complexité impliqué).
MemeDeveloper
13
+ 10 ^ 100 Trop d'abstraction est un mauvais 'motif'
Makach
57
-1. "EF sera TOUJOURS lent et inefficace." Je ne vois pas pourquoi vous affirmeriez que quelque chose comme ça est la vérité absolue. Avoir plus de couches à traverser ralentira quelque chose, mais le fait que cette différence soit même REMARQUABLE dépend entièrement de la situation, comme la quantité de données et le type de requête en cours d'exécution. Pour moi, c'est la même chose que de dire «C # sera TOUJOURS lent et inefficace» parce que c'est une abstraction plus élevée que C ++. Pourtant, de nombreuses personnes choisissent de l'utiliser car les gains de productivité dépassent de loin la perte de performance (s'il y en a). Il en va de même pour EF
Despertar
37

Une suggestion consiste à utiliser LINQ to Entity Framework uniquement pour les instructions CRUD à enregistrement unique.

Pour des requêtes, des recherches, des rapports, etc. plus complexes, écrivez une procédure stockée et ajoutez-la au modèle Entity Framework comme décrit sur MSDN .

C'est l'approche que j'ai adoptée avec quelques-uns de mes sites et cela semble être un bon compromis entre productivité et performance. Entity Framework ne génère pas toujours le SQL le plus efficace pour la tâche à accomplir. Et plutôt que de passer le temps à comprendre pourquoi, l'écriture d'une procédure stockée pour les requêtes les plus complexes me fait gagner du temps. Une fois que vous êtes familiarisé avec le processus, il n'est pas trop compliqué d'ajouter des processus stockés à votre modèle EF. Et bien sûr, l'avantage de l'ajouter à votre modèle est que vous obtenez toute cette bonté fortement typée qui vient de l'utilisation d'un ORM.

Steve Wortham
la source
Avez-vous une idée des méthodes utilisées dans l'échafaudage comme db.athlete.find (id), etc. Comment fonctionnent-elles par rapport à ADO.NET ou dapper ??
C'est un piège
15

Si vous êtes récupérez uniquement des données, c'est une grande aide pour les performances lorsque vous dites à EF de ne pas garder une trace des entités qu'il récupère. Pour ce faire, utilisez MergeOption.NoTracking. EF générera simplement la requête, l'exécutera et désérialisera les résultats en objets, mais n'essaiera pas de suivre les modifications d'entité ou quoi que ce soit de cette nature. Si une requête est simple (ne passe pas beaucoup de temps à attendre le retour de la base de données), j'ai constaté que la définir sur NoTracking peut doubler les performances des requêtes.

Consultez cet article MSDN sur l'énumération MergeOption:

Résolution d'identité, gestion de l'état et suivi des modifications

Cela semble être un bon article sur les performances d'EF:

Performance et Entity Framework

JulianR
la source
9
Avant que quiconque ne fasse cela, il peut être judicieux de lire ici. stackoverflow.com/questions/9259480/…
leen3o
6

Vous dites que vous avez profilé l'application. Avez-vous également profilé l'ORM? Il existe un profileur EF d'Ayende qui mettra en évidence les endroits où vous pouvez optimiser votre code EF. Vous pouvez le trouver ici:

http://efprof.com/

N'oubliez pas que vous pouvez utiliser une approche SQL traditionnelle avec votre ORM si vous avez besoin de gagner en performances.

S'il existe un ORM plus rapide / meilleur? Selon votre modèle d'objet / de données, vous pouvez envisager d'utiliser l'un des micro-ORM, tels que Dapper , Massive ou PetaPoco .

Le site Dapper publie quelques benchmarks comparatifs qui vous donneront une idée de leur comparaison avec d'autres ORM. Mais il convient de noter que les micro-ORM ne prennent pas en charge le riche ensemble de fonctionnalités des ORM complets tels que EF et NH.

Vous voudrez peut-être jeter un oeil à RavenDB . Il s'agit d'une base de données non relationnelle (d'Ayende encore une fois) qui vous permet de stocker des POCO directement sans aucun mappage nécessaire . RavenDB est optimisé pour les lectures et rend la vie des développeurs beaucoup plus facile en supprimant le besoin de manipuler le schéma et de mapper vos objets à ce schéma. Cependant, sachez qu'il s'agit d'une approche très différente de l'utilisation d'une approche ORM et que celles-ci sont décrites sur le site du produit .

Sean Kearon
la source
3

J'ai trouvé la réponse de @Slauma ici très utile pour accélérer les choses. J'ai utilisé le même type de modèle pour les insertions et les mises à jour - et les performances ont explosé.

MemeDeveloper
la source
2

D'après mon expérience, le problème n'est pas avec EF, mais avec l'approche ORM elle-même.

En général, tous les ORM souffrent de problèmes N + 1 , pas de requêtes optimisées, etc.

Valera Kolupaev
la source
1
Les gens me disent ça. Mais je vais mettre en place une instruction select simple en utilisant ADO old school, et la même sélection simple en utilisant un contexte EF et EF est toujours considérablement plus lente. Je veux vraiment aimer EF, mais cela rend la vie plus difficile au lieu de plus facile.
Sinaesthetic
1
@Sinaesthetic Bien sûr, c'est plus lent. De même, le code écrit en utilisant Linq to Objects est généralement plus lent que le code écrit sans lui. La question n'est pas vraiment de savoir si c'est plus rapide ou aussi rapide (comment cela pourrait-il être, quand sous le capot, il doit encore émettre la requête que vous émettiez à la main?) Mais si 1) c'est encore assez rapide pour vos besoins 2) cela économise vous le temps d'écrire le code 3) les avantages compensent les coûts. Sur la base de ces éléments, je pense que EF est approprié pour de nombreux projets.
Casey
@Sinaesthetic J'ajouterais également que si vous n'utilisez pas d'ORM, ce qui se passe le plus souvent, ce n'est pas que chaque requête SQL soit affinée et optimisée mais que l'application finit par développer un interne, organique, mal -ORM soutenu et peu performant, sauf si votre équipe est exceptionnellement disciplinée et très préoccupée par les performances.
Casey
1

J'ai également rencontré ce problème. Je déteste vider sur EF parce que cela fonctionne si bien, mais c'est juste lent. Dans la plupart des cas, je veux juste trouver un enregistrement ou une mise à jour / une insertion. Même les opérations simples comme celle-ci sont lentes. J'ai récupéré 1100 enregistrements d'une table dans une liste et cette opération a pris 6 secondes avec EF. Pour moi, c'est trop long, même épargner prend trop de temps.

J'ai fini par créer mon propre ORM. J'ai extrait les mêmes 1100 enregistrements d'une base de données et mon ORM a pris 2 secondes, beaucoup plus rapidement que EF. Tout avec mon ORM est presque instantané. La seule limitation pour le moment est qu'il ne fonctionne qu'avec MS SQL Server, mais il pourrait être modifié pour fonctionner avec d'autres comme Oracle. J'utilise MS SQL Server pour tout en ce moment.

Si vous souhaitez essayer mon ORM, voici le lien et le site Web:

https://github.com/jdemeuse1204/OR-M-Data-Entities

Ou si vous souhaitez utiliser nugget:

PM> Install-Package OR-M_DataEntities

La documentation est là aussi

TheMiddleMan
la source
0

Cela n'a de sens d'optimiser qu'après avoir profilé. Si vous découvrez que l'accès à la base de données est lent, vous pouvez utiliser des procédures stockées et conserver EF. Si vous découvrez que c'est l'EF lui-même qui est lent, vous devrez peut-être passer à un ORM différent ou ne pas utiliser du tout un ORM.

Gabe
la source
0

Nous avons une application similaire (Wcf -> EF -> base de données) qui fait 120 requêtes par seconde facilement, donc je suis plus que sûr que EF n'est pas votre problème ici, cela étant dit, j'ai vu des améliorations de performances majeures avec les requêtes compilées.

np-dur
la source
98% de mon code sont des appels de création et de mise à jour. Je ne sais pas si cela fait une différence, mais c'est beaucoup plus lent que 120 par seconde.
Vaccano
oui ce ne serait pas une application typique, je vous suggère de profiler votre application. pour nous, il se lit principalement ...
np-hard
0

J'ai utilisé EF, LINQ to SQL et dapper. Dapper est le plus rapide. Exemple: j'avais besoin de 1000 enregistrements principaux avec 4 sous-enregistrements chacun. J'ai utilisé LINQ pour SQL, cela a pris environ 6 secondes. Je suis ensuite passé à dapper, j'ai récupéré 2 jeux d'enregistrements de la procédure stockée unique et pour chaque enregistrement ajouté les sous-enregistrements. Temps total 1 seconde.

De plus, la procédure stockée utilisait des fonctions de valeur de table avec application croisée, j'ai trouvé que les fonctions de valeur scalaire étaient très lentes.

Mon conseil serait d'utiliser EF ou LINQ to SQL et, dans certaines situations, passer à dapper.

tfa
la source
-1

Entity Framework ne doit pas causer de goulots d'étranglement majeurs en soi. Il y a de fortes chances qu'il y ait d'autres causes. Vous pouvez essayer de basculer EF vers Linq2SQL, les deux ont des fonctionnalités de comparaison et le code devrait être facile à convertir, mais dans de nombreux cas, Linq2SQL est plus rapide que EF.

Wiktor Zychla
la source