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

Posted

技术标签:

【中文标题】如何实现 IEqualityComparer 以返回不同的值?【英文标题】:How to implement IEqualityComparer to return distinct values? 【发布时间】:2012-01-23 13:21:56 【问题描述】:

我有一个 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() 更好的解决方案——就像在this question 上提到的in the top rated answer。 【参考方案1】:

EqualityComparer 不是要走的路 - 它只能过滤内存中的结果集,例如:

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

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

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

【讨论】:

+1 这是一个救命稻草,但请注意,您不能使用 .First() 而必须使用 .FirstOrDefault() 我欠你一个教育!我希望我能投票的答案之一! @yoelhalb GroupBy 不保证返回的分组都不为空吗?返回的分组之一不可能为空,因为分组是通过分离元素形成的 @vijrox 我相信@yoelhalb 所指的LINQ to SQL 提供程序不支持IQueryable.First 方法-但它确实支持IQueryable.FirstOrDefault 方法。在这种情况下,正如您所说,两者在逻辑上都会返回相同的结果(但在提供程序中仅实现了两种方法中的一种)。 如果您尝试执行此操作,则在您从数据库加载数据后,例如,如果您想在 ObservableCollection 上执行此操作,您必须使用 asQueryable().GroupBy(o =&gt; o.Id).Select(c =&gt; c.FirstOrDefault()) 【参考方案2】:

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

他们的两个答案都涉及IEqualityComparer&lt;T&gt; 的方法不会被转换为 SQL 的事实。

我认为值得看看每个人的优缺点,这将需要更多的评论。

rich 的方法将查询重写为具有相同最终结果的不同查询。他们的代码应该或多或少地导致您如何使用手工编码的 SQL 有效地执行此操作。

Ladislav's 将它从数据库中提取出来,然后在内存中的方法将起作用。

由于数据库非常擅长对富人进行分组和过滤,因此在这种情况下它可能是性能最高的。您可能会发现在此分组之前发生的事情的复杂性使得 Linq-to-entities 不能很好地生成单个查询,而是生成一堆查询,然后在内存中完成一些工作,这可能很讨厌。

在内存中的情况下,通常分组比区分更昂贵(特别是如果您使用AsList() 而不是AsEnumerable() 将其放入内存)。因此,如果由于某些其他要求,您已经在此阶段将其放入内存中,那么它的性能会更高。

如果您的等式定义与数据库中可用的内容没有很好的相关性,这也是唯一的选择,当然,如果您想基于IEqualityComparer&lt;T&gt; 作为参数传递。

总而言之,富人是我想说的最有可能成为最佳选择的答案,但与富人相比,拉迪斯拉夫的优缺点各不相同,因此也值得研究和考虑。

【讨论】:

【参考方案3】:

你不会的。 Distinct 运算符在数据库上调用,因此您在应用程序中编写的任何代码都不能使用(您不能将相等比较器逻辑移动到 SQL),除非您对加载所有非不同值并在应用程序中进行不同过滤感到满意。

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

【讨论】:

为什么是ToList() 而不是ToEnumerable()【参考方案4】:

迟到的答案,但你可以做得更好: 如果 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 没有任何过载

【讨论】:

而不是return Id == 0 ? 0 : Id;,它可能只是return Id;

以上是关于如何实现 IEqualityComparer 以返回不同的值?的主要内容,如果未能解决你的问题,请参考以下文章

在 IEqualityComparer 中包装委托

如何在不可变的泛型 Pair 结构上实现 IEqualityComparer?

使用带有容差的 IEqualityComparer GetHashCode

c#List结合IEqualityComparer求交集

通用 IEqualityComparer 使用反射和属性来标记我想要比较的内容

List Except 操作,IEqualityComparer 使用