45 votes

Comment compter les entités associées sans les récupérer dans Entity Framework ?

Cela fait un moment que je m'interroge sur ce sujet, et j'ai donc pensé qu'il valait la peine d'utiliser mon premier post Stack Overflow pour poser la question.

Imaginons que j'aie une discussion à laquelle est associée une liste de messages :

DiscussionCategory discussionCategory = _repository.GetDiscussionCategory(id);

discussionCategory.Discussions est une liste d'entités Discussion qui n'est pas encore chargée.

Ce que je veux, c'est pouvoir parcourir les discussions d'une discussionCategory et dire combien de messages se trouvent dans chaque discussion sans avoir à récupérer les données des messages.

Lorsque j'ai essayé cela auparavant, j'ai dû charger les discussions et les messages pour pouvoir faire quelque chose comme cela :

discussionCategory.Discussions.Attach(Model.Discussions.CreateSourceQuery().Include("Messages").AsEnumerable());

foreach(Discussion discussion in discussionCategory.Discussions)
{

int messageCount = discussion.Messages.Count;

Console.WriteLine(messageCount);

}

Cela me semble plutôt inefficace, car j'extrais potentiellement des centaines de corps de messages de la base de données et je les garde en mémoire alors que tout ce que je souhaite faire, c'est compter leur nombre à des fins de présentation.

J'ai vu quelques questions qui abordaient ce sujet, mais elles ne semblaient pas l'aborder directement.

Merci d'avance pour toute réflexion que vous pourriez avoir sur ce sujet.

Mise à jour - Un peu plus de code comme demandé :

public ActionResult Details(int id)
    {  
        Project project = _repository.GetProject(id);
        return View(project);
    }

Ensuite, dans la vue (juste pour tester) :

Model.Discussions.Load();
var items = from d in Model.Discussions select new { Id = d.Id, Name = d.Name, MessageCount = d.Messages.Count() };

foreach (var item in items) {
//etc

J'espère que mon problème est un peu plus clair. Faites-moi savoir si vous avez besoin de plus de détails sur le code.

42voto

Craig Stuntz Points 95965

Facile, il suffit de se projeter sur un type de POCO (ou anonyme) :

var q = from d in Model.Discussions
        select new DiscussionPresentation
        {
            Subject = d.Subject,
            MessageCount = d.Messages.Count(),
        };

Lorsque vous regardez le code SQL généré, vous verrez que le champ Count() est effectuée par le serveur de la base de données.

Notez que cela fonctionne à la fois pour EF 1 et EF 4.

0 votes

Bonjour, merci pour la réponse. Malheureusement, j'ai déjà essayé cette méthode et j'ai constaté que la propriété count du type anonyme renvoyait zéro pour toutes les discussions. J'ai réessayé cette méthode après avoir vu votre réponse, mais j'ai obtenu le même résultat. Peut-être ai-je mal compris quelque chose à propos du cadre en ce qui concerne le maintien des entités "connectées" au magasin de données. Quelqu'un d'autre peut-il confirmer que la méthode ci-dessus devrait fonctionner ?

0 votes

Montrez votre code ; vous faites quelque chose de mal. Nous utilisons cette fonction largement dans nos applications de transport. Je soupçonne que vous essayez de le faire sur une propriété d'association EF (comme dans votre question) plutôt que dans une requête L2E (comme dans ma réponse). Les deux sont complètement différents ; le premier est LINQ to Objects ; le second est LINQ to Entities.

0 votes

Le code que j'ai utilisé était identique à celui que vous avez fourni. Il y a peut-être un problème plus haut dans la chaîne. Je vais voir si je peux modifier mon message original pour fournir un code supplémentaire si vous pensez que cela peut aider. Merci encore pour votre aide. Je suis en train de jeter un coup d'œil à l'article de blog auquel vous avez renvoyé.

10voto

Neil Laslett Points 423

Je sais que c'est une vieille question mais il semble que ce soit un problème récurrent et aucune des réponses ci-dessus ne fournit une bonne façon de traiter les agrégats SQL dans les vues en liste.

Je suppose qu'il s'agit de modèles POCO et de Code First comme dans les modèles et les exemples. Bien que la solution SQL View soit intéressante du point de vue de l'administrateur de bases de données, elle réintroduit le défi de maintenir en parallèle le code et les structures de bases de données. Pour les simples requêtes SQL agrégées, vous ne verrez pas beaucoup de gain de vitesse avec une vue. Ce qu'il faut vraiment éviter, ce sont les requêtes multiples (n+1) dans la base de données, comme dans les exemples ci-dessus. Si vous avez 5000 entités parents et que vous comptez les entités enfants (par exemple, les messages par discussion), cela représente 5001 requêtes SQL.

Vous pouvez renvoyer tous ces chiffres dans une seule requête SQL. Voici comment procéder.

  1. Ajoutez une propriété de remplacement à votre modèle de classe à l'aide de la méthode [NotMapped] de l'annotation de données de la System.ComponentModel.DataAnnotations.Schema espace de noms. Cela vous permet de stocker les données calculées sans avoir à ajouter une colonne à votre base de données ou à projeter des modèles de vue inutiles.

    ...
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace MyProject.Models
    {
        public class Discussion
        {
            [Key]
            public int ID { get; set; }
    
            ...
    
            [NotMapped]
            public int MessageCount { get; set; }
    
            public virtual ICollection<Message> Messages { get; set; }
        }
    }
  2. Dans votre contrôleur, obtenez la liste des objets parents.

    var discussions = db.Discussions.ToList();
  3. Saisir les comptes dans un dictionnaire. Cela génère une seule requête SQL GROUP BY avec tous les identifiants des parents et les comptages des objets enfants. (En supposant que DiscussionID est le FK en Messages .)

    var _counts = db.Messages.GroupBy(m => m.DiscussionID).ToDictionary(d => d.Key, d => d.Count());
  4. Bouclez les objets parents, recherchez le nombre dans le dictionnaire et stockez-le dans la propriété placeholder.

    foreach (var d in discussions)
        {
            d.MessageCount = (_counts.ContainsKey(d.ID)) ? _counts[d.ID] : 0;
        }
  5. Renvoyez votre liste de discussion.

    return View(discussions);
  6. Référencez le MessageCount dans la vue.

    @foreach (var item in Model) {
        ...
        @item.MessageCount
        ...
    }

Oui, vous pourriez simplement mettre ce dictionnaire dans le ViewBag et faire la recherche directement dans la vue, mais cela embrouille votre vue avec du code qui n'a pas besoin d'être là.

En fin de compte, j'aurais aimé que l'EF ait un moyen de faire du "comptage paresseux". Le problème avec le chargement paresseux et explicite est que vous êtes chargement les objets. Et s'il faut charger pour compter, c'est un problème potentiel de performance. Le comptage paresseux ne résoudrait pas le problème n+1 dans les vues en liste, mais il serait certainement agréable de pouvoir simplement appeler @item.Messages.Count à partir de la vue sans avoir à s'inquiéter du chargement potentiel de tonnes de données d'objets indésirables.

J'espère que cela vous aidera.

10voto

Ricardo Points 71

Si vous utilisez Entity Framework 4.1 ou une version ultérieure, vous pouvez utiliser :

var discussion = _repository.GetDiscussionCategory(id);

// Count how many messages the discussion has 
var messageCount = context.Entry(discussion)
                      .Collection(d => d.Messages)
                      .Query()
                      .Count();

Fuente: http://msdn.microsoft.com/en-US/data/jj574232

1voto

Dunc Points 4360

S'il ne s'agit pas d'un cas unique et que vous avez besoin de compter un certain nombre d'entités associées différentes, une vue de base de données peut être un choix plus simple (et potentiellement plus approprié) :

  1. Créez votre vue de la base de données.

    En supposant que vous souhaitiez obtenir toutes les propriétés de l'entité d'origine ainsi que le nombre de messages associés :

    CREATE VIEW DiscussionCategoryWithStats AS
    SELECT dc.*,
          (SELECT count(1) FROM Messages m WHERE m.DiscussionCategoryId = dc.Id)
              AS MessageCount
    FROM DiscussionCategory dc

    (Si vous utilisez Entity Framework Code First Migrations, voir cette réponse de l'OS sur la façon de créer une vue).

  2. Dans EF, il suffit d'utiliser la vue au lieu de l'entité d'origine :

    // You'll need to implement this!
    DiscussionCategoryWithStats dcs = _repository.GetDiscussionCategoryWithStats(id);
    
    int i = dcs.MessageCount;
    ...

0voto

Brian Hasden Points 2501

Je n'ai pas de réponse directe, mais je peux vous renvoyer à la comparaison suivante entre NHibernate et EF 4.0, qui semble suggérer que même dans EF 4.0, il n'y a pas de prise en charge immédiate de l'obtention des comptes d'une collection d'entités liées sans récupérer la collection.

http://ayende.com/Blog/archive/2010/01/05/nhibernate-vs.-entity-framework-4.0.aspx

J'ai mis un mot sur votre question et je l'ai marquée d'un astérisque. J'espère que quelqu'un apportera une solution viable.

0 votes

Ce n'est pas grave. Comme je l'ai dit, je n'ai pas de réponse directe à sa question, mais il y a beaucoup de choses sur le web qui disent que les comptages sont soit difficiles, soit impossibles. Je n'ai pas tendance à croire quelqu'un lorsqu'il dit que quelque chose est impossible, mais je voulais le présenter à l'auteur de la question et lancer la discussion.

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