49 votes

Combien d'Include puis-je utiliser sur ObjectSet dans EntityFramework pour conserver la performance ?

J'utilise la requête LINQ suivante pour ma page de profil :

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .Include("UserInterests.Interest")
                        .Include("UserMessengers.Messenger")
                        .Include("UserFriends.User.UserSkills.Skill")
                        .Include("UserFriends1.User1.UserSkills.Skill")
                        .Include("UserFriends.User.UserIdeas")
                        .Include("UserFriends1.User1.UserIdeas")
                               where u.UserId == userId
                               select u;

Il a un long graphe d'objets et utilise de nombreuses inclusions. Il fonctionne parfaitement pour l'instant, mais lorsque le site comptera de nombreux utilisateurs, cela aura-t-il un impact important sur les performances ?

Dois-je procéder autrement ?

87voto

Ladislav Mrnka Points 218632

Une requête avec des includes renvoie un seul ensemble de résultats et le nombre de includes affecte la manière dont l'ensemble de données est transféré du serveur de base de données au serveur web. Exemple :

Supposons que nous ayons une entité Customer (Id, Name, Address) et une entité Order (Id, CustomerId, Date) . Nous voulons maintenant interroger un client sur ses commandes :

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == 1);

L'ensemble de données obtenu aura la structure suivante :

 Id | Name | Address | OrderId | CustomerId | Date 
---------------------------------------------------
  1 |  A   |   XYZ   |    1    |     1      | 1.1.
  1 |  A   |   XYZ   |    2    |     1      | 2.1.

Cela signifie que Cutomers sont répétées pour chaque Order . Étendons maintenant l'exemple avec une autre entité - "OrderLine (Id, OrderId, ProductId, Quantity)". and Produit (Id, Nom)`. Nous voulons maintenant interroger un client avec ses commandes, ses lignes de commande et ses produits :

var customer = context.Customers
                      .Include("Orders.OrderLines.Product")
                      .SingleOrDefault(c => c.Id == 1);

L'ensemble de données obtenu aura la structure suivante :

 Id | Name | Address | OrderId | CustomerId | Date | OrderLineId | LOrderId | LProductId | Quantity | ProductId | ProductName
------------------------------------------------------------------------------------------------------------------------------
  1 |  A   |   XYZ   |    1    |     1      | 1.1. |     1       |    1     |     1      |    5     |    1      |     AA
  1 |  A   |   XYZ   |    1    |     1      | 1.1. |     2       |    1     |     2      |    2     |    2      |     BB
  1 |  A   |   XYZ   |    2    |     1      | 2.1. |     3       |    2     |     1      |    4     |    1      |     AA
  1 |  A   |   XYZ   |    2    |     1      | 2.1. |     4       |    2     |     3      |    6     |    3      |     CC

Comme vous pouvez le constater, les données sont souvent dupliquées. En général, chaque inclusion à une propriété de navigation de référence ( Product dans l'exemple) ajoutera de nouvelles colonnes et chaque inclusion à une propriété de navigation de la collection ( Orders y OrderLines dans l'exemple) ajoutera de nouvelles colonnes et dupliquera les lignes déjà créées pour chaque ligne de la collection incluse.

Cela signifie que votre exemple peut facilement comporter des centaines de colonnes et des milliers de lignes, ce qui représente beaucoup de données à transférer. La bonne approche consiste à créer des tests de performance et si le résultat ne répond pas à vos attentes, vous pouvez modifier votre requête et charger les propriétés de navigation séparément avec leurs propres requêtes ou par LoadProperty méthode.

Exemple de requêtes distinctes :

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == 1);
var orderLines = context.OrderLines
                        .Include("Product")
                        .Where(l => l.Order.Customer.Id == 1)
                        .ToList();

Exemple de LoadProperty :

var customer = context.Customers
                      .SingleOrDefault(c => c.Id == 1);
context.LoadProperty(customer, c => c.Orders);

De même, vous ne devez charger que les données dont vous avez réellement besoin.

Editer : Je viens de créer proposition sur les données UserVoice pour prendre en charge une stratégie supplémentaire de chargement anticipé, dans le cadre de laquelle les données chargées anticipativement seraient transmises dans un ensemble de résultats supplémentaire (créé par une requête distincte au cours d'un même aller-retour dans la base de données). Si vous trouvez cette amélioration intéressante, n'oubliez pas de voter pour la proposition.

1 votes

+5 (si possible) Je n'étais pas au courant de cela et j'avais l'habitude de travailler avec le chargement anticipé de manière plutôt naïve. C'est un très bon exemple du fait que la connaissance du "R" dans "ORM" est encore nécessaire pour utiliser un ORM efficacement - malheureusement...

15voto

Sampath Points 4405

(Vous pouvez améliorer les performances de nombreux éléments en créant 2 ou plusieurs petites demandes de données à partir de la base de données comme ci-dessous.

D'après mon expérience, Only peut donner maximum 2 inclusions par requête Plus que cela, les performances seront vraiment mauvaises.

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .FirstOrDefault();

 userData = from u in db.Users
                    .Include("UserFriends.User.UserSkills.Skill")
                    .Include("UserFriends1.User1.UserSkills.Skill")
                    .FirstOrDefault();

Cette méthode permet d'extraire un petit ensemble de données de la base de données en utilisant davantage de déplacements vers la base de données.

0 votes

@MikeCole Merci d'avoir au moins profité de cet article.

1 votes

Bizarrement, vous avez raison, cela fonctionne. Dans mon cas, la requête unique combinée a échoué, mais les deux requêtes séparées fonctionnent.

8voto

Stephen Chung Points 9467

Oui, il le sera. Évitez d'utiliser Inclure s'il s'agit de développer plusieurs lignes de détail sur une ligne de la table maîtresse.

Je crois que EF convertit la requête en une grande jointure au lieu de plusieurs requêtes. Par conséquent, vous finirez par dupliquer les données de votre table principale sur chaque ligne de la table des détails.

Par exemple : Master -> Détails. Supposons que le fichier principal contienne 100 lignes et que le fichier détaillé contienne 5 000 lignes (50 pour chaque fichier principal).

Si vous chargez paresseusement les détails, vous obtenez 100 lignes (taille : maître) + 5000 lignes (taille : détails).

Si vous utilisez .Include("Details"), vous obtenez 5000 lignes (taille : master + details). En fait, la partie principale est dupliquée plus de 50 fois.

Il est multiplié par plusieurs si vous incluez plusieurs tableaux.

Vérifiez le code SQL généré par EF.

1 votes

+1 J'ai trouvé ça moi-même. Il est toujours préférable de créer un certain nombre de petites requêtes et de les exécuter séparément plutôt que de les exécuter une par une. Mais ce qui est bien, c'est que par la magie de EF, il construit automatiquement le graphe d'objets pour vous. Ainsi, si vous chargez vos utilisateurs dans une requête, puis vos compétences, ils apparaîtront automatiquement dans les propriétés de navigation des uns et des autres. (Je suppose qu'il s'agit de EF en général car j'utilise Code First).

0 votes

@Generic Type Tea, je crois que c'est général pour EF. En fait, je pense qu'ils construisent les propriétés de navigation lors du premier accès...

3voto

Darin Dimitrov Points 528142

Je vous recommande d'effectuer des tests de charge et de mesurer les performances du site en situation de stress. Si vous effectuez des requêtes complexes à chaque fois, vous pouvez envisager de mettre en cache certains résultats.

3voto

Bugeo Points 808

Le résultat de l'inclusion peut changer : il dépend de l'entité qui appelle la méthode d'inclusion.

Comme dans l'exemple proposé par Ladislav Mrnka, supposons que nous ayons une entité

Client (Id, Nom, Adresse)

qui renvoient à cette table :

Id  |  Name   | Address
-----------------------
C1  |  Paul   |   XYZ   

et une entité Order (Id, CustomerId, Total)

qui renvoient à cette table :

Id |  CustomerId  | Total
-----------------------
O1 |      C1      |  10.00
O2 |      C1      |  13.00

La relation est la suivante un client a Nombreuses commandes


Exemple 1 : Client => Commandes

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == "C1");

Linq sera traduit en une requête SQL très complexe.

Dans ce cas, la requête produira deux enregistrements et les informations sur le client seront répliquées.

 Customer.Id   |   Customer.Name |    Order.Id |  Order.Total
-----------------------------------------------------------
     C1        |       Paul      |       O1    |    10.00     
     C1        |       Paul      |       O2    |    13.00   

Exemple 2 : Commande => Client

var order = context.Orders
                      .Include("Customers")
                      .SingleOrDefault(c => c.Id == "O1");

Linq sera traduit en un simple sql Join.

Dans ce cas, la requête ne produira qu'un seul enregistrement sans duplication d'informations :

 Order.Id |  Order.Total |  Customer.Id   |   Customer.Name
-----------------------------------------------------------
     O1   |    10.00     |      C1        |       Paul

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