如何实现IEqualityComparer以返回不同的值?

70

我有一个L2E查询,返回了一些包含重复对象的数据。我需要删除那些重复的对象。基本上,如果它们的ID相同,那么它们就是重复的对象。我试过 q.Distinct(),但仍然返回重复的对象。然后我尝试实现自己的 IEqualityComparer 并将其传递给 Distinct() 方法。该方法失败并显示以下文本:

LINQ to Entities 不认识方法 'System.Linq.IQueryable1[DAL.MyDOClass] Distinct[MyDOClass](System.Linq.IQueryable1[DAL.MyDOClass], System.Collections.Generic.IEqualityComparer`1[DAL.MyDOClass])' 方法,因此无法将此方法转换为存储表达式。

这里是 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;
        }
    }

那么如何正确编写自己的 IEqualityComparer 呢?


GroupBy() 可能比 Distinct() 更好 - 就像在 这个问题最高评分答案 中提到的那样。 - Martin Zaloga
4个回答

165

EqualityComparer 不是一个好的选择 - 它只能在内存中过滤你的结果集,例如:

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

您可以使用GroupBy方法按ID分组,使用First方法使您的数据库只检索每个ID的唯一条目,例如:

var objects = yourResults.GroupBy(o => o.Id).Select(g => g.First());

12
这真是个救命稻草,不过请注意,你不能使用.First(),而必须使用.FirstOrDefault() - yoel halb
我欠你一份教育!那是我希望能够点赞的答案之一! - seebiscuit
1
@yoelhalb 不是说 GroupBy 保证返回的所有分组都不为空吗?因为分组是通过将元素分离而形成的,所以不可能有一个返回的分组是空的。 - vijrox
3
我相信@yoelhalb所指的LINQ to SQL提供程序不支持IQueryable.First方法,但它支持IQueryable.FirstOrDefault方法。在这种情况下,如你所说,两者逻辑上都会返回相同结果(但只有其中一个方法被实现在提供程序中)。 - Rich O'Kelly
哇,伙计,那确实是一个单行神奇的Distinct代码。 - Chandraprakash
显示剩余2条评论

22

rich.okelly和Ladislav Mrnka在不同的方面都是正确的。

他们的回答都涉及到IEqualityComparer<T>的方法不会被翻译为SQL的事实。

我认为值得探讨每种方法的优缺点,这需要比评论更多的时间。

rich的方法重写了查询,使其具有相同的最终结果。他们的代码应该会产生大致与手工编写SQL相同的效果。

Ladislav的方法在去重之前从数据库中提取数据,然后采用基于内存的方法来处理。

由于数据库非常擅长执行rich所依赖的分组和过滤操作,因此在这种情况下它很可能是最有效的。但是,由于在进行此分组之前正在发生的事情的复杂性可能会导致Linq-to-entities无法很好地生成单个查询,而是会生成一堆查询,并在内存中执行部分工作,这可能非常恶劣。

通常,在内存中进行分组比去重更昂贵(特别是如果您使用AsList()而不是AsEnumerable()将其带入内存)。因此,如果您已经由于其他要求而打算在此阶段将其带入内存,那么它将具有更好的性能。

如果您的相等定义与数据库中可用的内容不相关,那么它也将是唯一的选择;当然,它还允许您根据传递的IEqualityComparer<T>参数切换相等定义。

总的来说,我认为rich的回答最有可能是此处最佳选择,但Ladislav相对于rich的优缺点不同,因此值得深入研究和考虑。


9

您不能这么做。数据库中调用了distinct运算符,因此您在应用程序中编写的任何代码都无法使用(除非您愿意加载所有非唯一值并在应用程序中进行唯一过滤)。

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

5
为什么要使用 ToList() 而不是 ToEnumerable() - Jon Hanna

7
晚了点回答,但还可以更好: 如果DAL对象是部分的(通常是DB对象),您可以像这样扩展它:
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;
        }
    }

这个distinct函数将不带任何负荷工作。

如果没有,您可以像这样创建IEqualityComparer类:

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;
        }
    }

再次使用不带任何重载的Distinct


2
可以将 return Id == 0 ? 0 : Id; 简化为 return Id; - Kerrmiter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接