web-dev-qa-db-fra.com

Le code de l'entité est lent lorsque vous utilisez plusieurs fois Include ()

J'ai débogué du code lent et il semble que le coupable soit le code EF affiché ci-dessous. Cela prend 4 à 5 secondes lorsque la requête est évaluée ultérieurement. J'essaie de le faire fonctionner en moins d'une seconde. 

J'ai testé cela en utilisant SQL Server Profiler, et il semble qu'un tas de scripts SQL sont exécutés. Il confirme également qu'il faut 3 à 4 secondes avant que SQL Server ait terminé les exécutions.

J'ai lu d'autres questions similaires sur l'utilisation de Include () et il semble qu'il y ait une baisse de performance lors de son utilisation. J'ai essayé de scinder le code ci-dessous en plusieurs requêtes différentes, mais cela ne fait pas une grande différence.

Avez-vous une idée de la manière dont je peux obtenir l'exécution ci-dessous plus rapidement? 

Actuellement, l'application Web sur laquelle je travaille ne fait que montrer un iframe vide en attendant la fin de l'opération ci-dessous. Si je ne peux pas obtenir un temps d'exécution plus rapide, je dois le scinder et charger partiellement l'iframe avec des données ou utiliser une autre solution asynchrone. Toutes les idées ici seraient également appréciées!

using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
        {
            formInstance = context.FormInstanceSet
                                .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormSectionDefinitions).Include(fs => fs.FormStateDefinitionEditableSections))
                                .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormStateDefinitions))
                                .Includes(x => x.Include(fi => fi.FormSectionInstances).Include(fs => fs.FormFieldInstances).Include(ff => ff.FormFieldDefinition).Include(ffd => ffd.FormFieldMetaDataDefinition).Include(ffmdd => ffmdd.ComplexTypePropertyNames))
                                .Include(x => x.CurrentFormStateInstance)      
                                .Include(x => x.Files)
                                .FirstOrDefault(x => x.FormInstanceIdentifier == formInstanceIdentifier);

            scope.Complete();
        }
27
DSF

il semble qu'il y ait une pénalité de performance lors de l'utilisation de Include

C'est un euphémisme! Plusieurs Includes font exploser rapidement le résultat de la requête SQL en largeur et en longueur. Pourquoi donc?

tl; dr Multiple Includes explose le jeu de résultats SQL. Bientôt, il devient moins coûteux de charger des données par plusieurs appels de base de données au lieu d'exécuter une méga instruction. Essayez de trouver le meilleur mélange d’énoncés Include et Load.

Facteur de croissance de Includes

Disons que nous avons

  • entité racine Root
  • entité mère Root.Parent
  • entités enfants Root.Children1 et Root.Children2
  • une instruction LINQ Root.Include("Parent").Include("Children1").Include("Children2")

Cela crée une instruction SQL ayant la structure suivante:

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1

UNION

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2

Ces <PseudoColumns> sont constitués d'expressions telles que CAST(NULL AS int) AS [C2], et servent à avoir le même nombre de colonnes dans toutes les requêtes UNION-. La première partie ajoute des pseudo colonnes pour Child2, la deuxième partie ajoute des pseudo colonnes pour Child1.

Voici ce que cela signifie pour la taille du jeu de résultats SQL:

  • Le nombre de columns dans la clause SELECT est la somme de toutes les colonnes des quatre tables.
  • Le nombre de lignes est la somme des enregistrements des collections enfants incluses.

Étant donné que le nombre total de points de données est columns * rows, chaque Include supplémentaire augmente de manière exponentielle le nombre total de points de données dans le jeu de résultats. Permettez-moi de démontrer cela en reprenant Root, maintenant avec une collection Children3 supplémentaire. Si toutes les tables ont 5 colonnes et 100 lignes, on obtient:

Un Include (Root + 1 collection enfant): 10 colonnes * 100 lignes = 1000 points de données.
Deux Includes (Root + 2 collections enfants): 15 colonnes * 200 lignes = 3000 points de données.
Trois Includes (Root + 3 collections enfants): 20 colonnes * 300 lignes = 6000 points de données. 

Avec 12 Includes, cela équivaut à 78 000 points de données!

Inversement, si vous obtenez tous les enregistrements de chaque table séparément au lieu de 12 Includes, vous avez 13 * 5 * 100 points de données: 6500, moins de 10%!

Maintenant, ces chiffres sont quelque peu exagérés dans la mesure où bon nombre de ces points de données sont null. Ils ne contribuent donc pas beaucoup à la taille réelle du jeu de résultats envoyé au client. Mais la taille de la requête et la tâche de l'optimiseur de requête sont certainement affectées négativement par le nombre croissant de Includes.

Équilibre

Donc, utiliser Includes est un équilibre délicat entre le coût des appels de base de données et le volume de données. Il est difficile de donner une règle générale, mais vous pouvez maintenant imaginer que le volume de données dépasse rapidement le coût des appels supplémentaires s’il existe plus de ~ 3 Includes pour les collections enfants (mais un peu plus pour le parent Includes, qui seulement élargir le jeu de résultats).

Alternative

L'alternative à Include consiste à charger des données dans des requêtes distinctes:

context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);

Cela charge toutes les données requises dans le cache du contexte. Au cours de ce processus, EF exécute relationship fixup, ce qui lui permet de renseigner automatiquement les propriétés de navigation (Root.Children etc.) à l'aide d'entités chargées. Le résultat final est identique à l'instruction avec Includes, à une différence importante près: les collections enfants ne sont pas marquées comme chargées dans le gestionnaire d'état de l'entité. EF essaiera de déclencher le chargement différé si vous y accédez. C'est pourquoi il est important de désactiver le chargement paresseux.

En réalité, vous devrez déterminer quelle combinaison d'instructions Include et Load vous convient le mieux.

47
Gert Arnold

Avez-vous configuré correctement les relations entre toutes les entités que vous essayez d'inclure? Si au moins une entité n'a pas de relation avec d'autres entités, EF ne pourra pas construire une requête complexe à l'aide de la syntaxe de jointure SQL. Au lieu de cela, il exécutera autant de requêtes que nombre d'inclusions. Et bien sûr, cela entraînera des problèmes de performance. Pourriez-vous s'il vous plaît poster la requête exacte (-es) générée par EF afin d'obtenir les données?

0
drcolombo