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();
}
il semble qu'il y ait une pénalité de performance lors de l'utilisation de Include
C'est un euphémisme! Plusieurs Include
s font exploser rapidement le résultat de la requête SQL en largeur et en longueur. Pourquoi donc?
tl; dr Multiple Include
s 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
.
Include
sDisons que nous avons
Root
Root.Parent
Root.Children1
et Root.Children2
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:
SELECT
est la somme de toutes les colonnes des quatre tables.É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 Include
s (Root
+ 2 collections enfants): 15 colonnes * 200 lignes = 3000 points de données.
Trois Include
s (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 Include
s.
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).
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 Include
s, à 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.
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?