web-dev-qa-db-fra.com

Le nombre de base Entity Framework n'a pas des performances optimales

Je dois obtenir la quantité d'enregistrements avec un certain filtre.

Théoriquement cette instruction:

_dbContext.People.Count (w => w.Type == 1);

Il devrait générer du SQL comme:

Select count (*)
from People
Where Type = 1

Cependant, le SQL généré est:

Select Id, Name, Type, DateCreated, DateLastUpdate, Address
from People
Where Type = 1

La requête générée prend beaucoup plus de temps à s'exécuter dans une base de données contenant de nombreux enregistrements.

J'ai besoin de générer la première requête.

Si je fais juste ça:

_dbContext.People.Count ();

Entity Framework génère la requête suivante:

Select count (*)
from People

.. qui court très vite.

Comment générer cette seconde requête en passant des critères de recherche au compte?

14
Renatto Machado

Il n'y a pas grand chose à répondre ici. Si votre outil ORM ne produit pas la requête SQL attendue à partir d'une requête LINQ simple, vous ne pouvez pas le laisser faire en réécrivant la requête (et vous ne devriez pas le faire en premier lieu).

EF Core a le concept de évaluation mixte client/base de données dans les requêtes LINQ qui leur permet de publier des versions de EF Core avec un traitement de requête incomplet/très inefficace, comme dans votre cas.

Extrait de Fonctionnalités ne figurant pas dans EF Core (notez le mot pas) et Feuille de route :

Traduction améliorée pour permettre à davantage de requêtes de s'exécuter avec plus de logique en cours d'évaluation dans la base de données (plutôt qu'en mémoire).

Bientôt, ils envisagent d’améliorer le traitement des requêtes, mais nous ne savons pas quand cela se produira ni quel niveau de diplôme (rappelez-vous que le mode mixte leur permet d’envisager le fonctionnement de la requête).

Alors, quelles sont les options?

  • Tout d’abord, éloignez-vous de EF Core jusqu’à ce que cela devienne vraiment utile. Retournez à EF6, il n'y a pas de tels problèmes.
  • Si vous ne pouvez pas utiliser EF6, restez à jour avec la dernière version d'EF Core.

Par exemple, dans les versions 1.0.1 et 1.1.0, votre requête génère le code SQL voulu (testé), vous pouvez donc simplement effectuer une mise à niveau et le problème concret disparaîtra.

Cependant, notez que, parallèlement aux améliorations apportées, les nouvelles versions introduisent des bogues/régressions (comme vous pouvez le voir ici EFCorex renvoyant trop de colonnes pour une jointure LEFT OUTER simple par exemple), faites-le donc à vos risques option à nouveau, c'est-à-dire lequel vous convient :)

11
Ivan Stoev

Essayez d'utiliser cette expression lambda pour exécuter la requête plus rapidement. 

_dbContext.People.select(x=> x.id).Count();
4
Raju Mali

Si vous souhaitez optimiser les performances et que le fournisseur EF actuel n'est pas (encore) capable de produire la requête souhaitée, vous pouvez toujours vous fier à raw SQL

Évidemment, c’est un compromis car vous utilisez EF pour éviter d’écrire directement en SQL, mais l’utilisation de SQL brut peut être utile si la requête que vous souhaitez exécuter ne peut pas être exprimée à l’aide de LINQ, ou si l’utilisation d’une requête LINQ entraîne SQL inefficace envoyé à la base de données. 

Un exemple de requête SQL brute ressemblerait à ceci:

var results = _context.People.FromSql("SELECT Id, Name, Type, " +
                                      "FROM People " +
                                      "WHERE Type = @p0",                                                     
                                      1);

Pour autant que je sache, les requêtes SQL brutes transmises à la méthode d’extension FromSql exigent actuellement que vous renvoyiez un type de modèle, c’est-à-dire que renvoyer un résultat scalaire peut ne pas encore être pris en charge.

Vous pouvez toutefois toujours revenir aux requêtes ADO.NET simples:

using (var connection = _context.Database.GetDbConnection())
{
    connection.Open();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = "SELECT COUNT(*) FROM People WHERE Type = 1";
        var result = command.ExecuteScalar().ToString();
    }
}
3
Dirk Vollmar

Essaye ça

(from x in _dbContext.People where x.Type == 1 select x).Count();

ou vous pouvez en faire la version asynchrone comme:

await (from x in _dbContext.People where x.Type == 1 select x).CountAsync();

et si cela ne fonctionne pas pour vous, vous pouvez au moins rendre la requête plus efficace en faisant:

(from x in _dbContext.People where x.Type == 1 select x.Id).Count();

ou

await (from x in _dbContext.People where x.Type == 1 select x.Id).CountAsync();
3
Patrick Mcvay

Est-ce que cela donne ce que vous voulez:

_dbContext.People.Where(w => w.Type == 1).Count();
2
Theo

Il semble que l'une des premières versions d'Entity Framework Core pose problème. Malheureusement, vous n'avez pas spécifié la version exacte et je ne suis donc pas en mesure de creuser dans le code source EF pour dire ce qui ne va pas.

Pour tester ce scénario, j'ai installé le dernier package EF Core et j'ai réussi à obtenir un résultat correct.

Voici mon programme de test:  Source code

Et voici SQL qui est généré et capturé par SQL Server Profiler:  enter image description here

Comme vous pouvez le constater, il correspond à toutes les attentes.

Voici l'extrait du fichier packages.config:

...
<package id="Microsoft.EntityFrameworkCore" version="1.1.0" targetFramework="net452" />
...

Ainsi, dans votre situation, la seule solution consiste à mettre à jour le dernier package, 1.1.0, au moment de la rédaction de cet article.

2
Kaspars Ozols

désolé pour la bosse, mais ...

la raison pour laquelle la requête avec la clause where est lente est probablement due au fait que vous n'avez pas fourni à votre base de données un moyen rapide de l'exécuter.

dans le cas du nombre sélectionné (*) de la requête People, nous n'avons pas besoin de connaître les données réelles pour chaque champ et nous pouvons simplement utiliser un petit index qui ne contient pas tous ces champs, nous n'avons donc pas besoin de dépenser nos entrées/sorties lentes. sur. Le logiciel de base de données serait suffisamment intelligent pour constater que l’index de clé primaire nécessite le moins d’E/S sur lequel compter. Les identifiants de pk nécessitent moins d'espace que la ligne complète, ce qui vous permet de compter davantage par bloc d'E/S, ce qui vous permet de terminer plus rapidement.

Maintenant, dans le cas d'une requête avec le type, il doit lire le type pour déterminer sa valeur. Vous devez créer un index sur le type si vous voulez que votre requête soit rapide, sinon elle devra effectuer une analyse très lente de la table complète, en lisant toutes les lignes. Cela aide lorsque vos valeurs sont plus discriminantes. Une colonne Sexe (en général) ne comporte généralement que deux valeurs et n'est pas très discriminante. Une colonne de clé primaire où chaque valeur est unique est hautement discriminante. Des valeurs de discrimination plus élevées se traduiront par une analyse de la plage d'index plus courte et un résultat plus rapide pour le décompte. 

1

J'utilise EFCore 1.1 ici.

Cela peut se produire si EFCore ne peut pas traduire l'intégralité de la clause Where en SQL. Cela peut être quelque chose d'aussi simple que DateTime.Now auquel vous pourriez même ne pas penser.

L'instruction suivante aboutit à une requête SQL qui exécutera étonnamment un SELECT * puis un C # .Count() une fois qu'il aura chargé la table entière!

   int sentCount = ctx.ScheduledEmail.Where(x => x.template == template &&
                   x.SendConfirmedDate > DateTime.Now.AddDays(-7)).Count();

Mais cette requête exécutera une SELECT COUNT(*) SQL comme vous le souhaiteriez/espérez:

   DateTime earliestDate = DateTime.Now.AddDays(-7);
   int sentCount = ctx.ScheduledEmail.Where(x => x.template == template 
                   && x.SendConfirmedDate > earliestDate).Count();

Fou mais vrai. Heureusement, cela fonctionne aussi:

   DateTime now = DateTime.Now;
   int sentCount = ctx.ScheduledEmail.Where(x => x.template == template &&
                   x.SendConfirmedDate > now.AddDays(-7)).Count();
1
Simon_Weaver