web-dev-qa-db-fra.com

Comment implémenter IEqualityComparer pour retourner des valeurs distinctes?

J'ai une requête L2E qui renvoie des données contenant des objets en double. J'ai besoin de supprimer ces objets en double. Fondamentalement, je devrais supposer que si leurs identifiants sont les mêmes, les objets sont en double. J'ai essayé q.Distinct(), mais cela a quand même renvoyé des objets en double. Ensuite, j'ai essayé d'implémenter mon propre IEqualityComparer et de le passer à la méthode Distinct(). La méthode a échoué avec le texte suivant:

LINQ to Entities ne reconnaît pas la méthode 'System.Linq.IQueryable1[DAL.MyDOClass] Distinct[MyDOClass](System.Linq.IQueryable1 [DAL.MyDOClass], System.Collections.Generic.IEqualityComparer`1 [DAL.MyDOClass]) ', et cette méthode ne peut pas être traduite en une expression de magasin.

Et voici l'implémentation d'EqualityComparer:

  internal class MyDOClassComparer: EqualityComparer<MyDOClass>
    {
        public override bool Equals(MyDOClass x, MyDOClass y)
        {
            return x.Id == y.Id;
        }

        public override int GetHashCode(MyDOClass obj)
        {
            return obj == null ? 0 : obj.Id;
        }
    }

Alors, comment puis-je écrire correctement mon IEqualityComparer?

43
Bogdan Verbenets

Un EqualityComparer n'est pas le chemin à parcourir - il ne peut que filtrer votre jeu de résultats en mémoire, par exemple:

var objects = yourResults.ToEnumerable().Distinct(yourEqualityComparer);

Vous pouvez utiliser la méthode GroupBy pour regrouper par ID et la méthode First pour permettre à votre base de données de récupérer uniquement une entrée unique par ID, par exemple:

var objects = yourResults.GroupBy(o => o.Id).Select(g => g.First());
116
Rich O'Kelly

rich.okelly et Ladislav Mrnka ont tous deux raison de différentes manières.

Leurs deux réponses traitent du fait que les méthodes de IEqualityComparer<T> Ne seront pas traduites en SQL.

Je pense que cela vaut la peine d'examiner les avantages et les inconvénients de chacun, ce qui prendra un peu plus qu'un commentaire.

l'approche de Rich réécrit la requête dans une autre requête avec le même résultat final. Leur code devrait résulter plus ou moins de la façon dont vous le feriez efficacement avec du SQL codé à la main.

Ladislav le sort de la base de données au point avant le distinct, puis une approche en mémoire fonctionnera.

Étant donné que la base de données est excellente pour effectuer le type de regroupement et de filtrage des riches, elle sera probablement la plus performante dans ce cas. Vous pourriez cependant constater que la complexité de ce qui se passe avant ce regroupement est telle que Linq-to-entity ne génère pas bien une seule requête, mais produit plutôt un tas de requêtes, puis effectue une partie du travail en mémoire, ce qui pourrait être assez méchant.

Généralement, le regroupement est plus cher que distinct dans les cas en mémoire (surtout si vous le mettez en mémoire avec AsList() plutôt que AsEnumerable()). Donc, si vous alliez déjà le mettre en mémoire à ce stade en raison d'une autre exigence, ce serait plus performant.

Ce serait également le seul choix si votre définition d'égalité était quelque chose qui ne correspondait pas bien à ce qui est disponible uniquement dans la base de données, et bien sûr, cela vous permet de changer de définition d'égalité si vous le souhaitez en fonction d'un IEqualityComparer<T> Passé en paramètre.

Dans l'ensemble, le riche est la réponse que je dirais serait probablement le meilleur choix ici, mais les différents avantages et inconvénients de Ladislav par rapport aux riches le rendent également intéressant à étudier et à considérer.

16
Jon Hanna

Tu ne vas pas. L'opérateur Distinct est appelé dans la base de données, de sorte que le code que vous écrivez dans votre application ne peut pas être utilisé (vous ne pouvez pas déplacer votre logique de comparateur d'égalité vers SQL), sauf si vous êtes satisfait du chargement de toutes les valeurs non distinctes et du filtrage distinct dans ton application.

var query = (from x in context.EntitySet where ...).ToList()
                                                   .Distinct(yourComparer);
7
Ladislav Mrnka

Réponse tardive mais vous pouvez faire mieux: si l'objet DAL est partiel (généralement s'il s'agit d'un objet DB), vous pouvez l'étendre comme ceci:

public partial class MyDOClass :  IEquatable<MyDOClass>
    {

        public override int GetHashCode()
        {
            return Id == 0 ? 0 : Id;
        }

        public bool Equals(MyDOClass other)
        {
            return this.Id == other.Id;
        }
    }

Et le distinct fonctionnera sans aucune surcharge.

Sinon, vous pouvez créer la classe IEqualityComparer comme ceci:

internal class MyDOClassComparer : MyDOClass,  IEquatable<MyDOClass>, IEqualityComparer<MyDOClass>
    {
        public override int GetHashCode()
        {
            return Id == 0 ? 0 : Id;
        }

        public bool Equals(MyDOClass other)
        {
            return this.Id == other.Id;
        }

        public bool Equals(MyDOClass x, MyDOClass y)
        {
            return x.Id == y.Id;
        }

        public int GetHashCode(MyDOClass obj)
        {
            return Id == 0 ? 0 : Id;
        }
    }

Et encore une fois, utilisez le Distinct sans aucune surcharge

2
gil kr

GroupBy() peut être une meilleure solution que Distinct() - comme mentionné dans la réponse la mieux notée sur cette question .

2
Martin Zaloga