与linq到实体和自定义IEqualityComparer的不同计数

时间:2011-02-08 07:37:13

标签: c# linq linq-to-entities distinct

我的自定义比较器似乎不起作用。我想要一些不同的对象,但我每次都得到1。即使查看数据库本身清楚地显示查询还有1个具有不同“TimeOfAction”值的实例。

 class TimeComparer : IEqualityComparer<Action>
{
    public bool Equals(Action a, Action b)
    {
        if (a.TimeOfAction == b.TimeOfAction)
            return true;
        else
            return false;
    }

    public int GetHashCode(Action obj)
    {
        return obj.ToString().ToLower().GetHashCode();
    }
}

认为它可能是GetHashCode方法,因为我不太熟悉它的工作方式。这是linq查询。我转换为AsEnumerable,因为Linq to Entities不支持distinct方法。

DBEntities db = new DBEntities();

        IEnumerable<Action> query = 
                    from action in db.Action.AsEnumerable()
                    where action.TimeOfAction > new DateTime(2010, 11, 1, 0, 0, 0)
                    where action.TimeOfAction < new DateTime(2011, 2, 7, 0, 0, 0)
                    where action.EntityName == "seant"
                    select action;

var count = query.   
            Distinct(new TimeComparer()).Count();

2 个答案:

答案 0 :(得分:6)

您的Equals和GetHashCode方法正在采用完全不同的方法。特别是,假设Action.ToString使用TimeOfAction以外的字段,则相等的对象可能具有不同的哈希码。他们必须协调一致,否则你将无法获得合理的结果。不等对象具有相同的哈希码是可以的(尽管这会妨碍性能)但是相同的对象必须给出相同的哈希码。

请注意,使用自定义比较器会强制Distinct部分在进程中而不是在数据库中完成。这可能不是问题,你只需要了解它。编辑:我没有发现 <{1}} Queryable.Distinct超载IEqualityComparer<T>。我的猜测是,你可以提供自定义字符串比较器和一些其他众所周知的比较器...而不仅仅是任意代码。如果它有效,它无论如何都将在本地完成。如果它只是爆炸,我不会感到惊讶。

编辑:正如Marc所说,您可以使用Select(x => x.TimeOfAction).Distinct().Count()在数据库中执行此操作。您也需要删除对AsEnumerable的调用。我的猜测就是那里,因为其他东西不起作用。你可以试试这个:

DBEntities db = new DBEntities();
IQueryable<DateTime> query = 
            from action in db.Action
            where action.TimeOfAction > new DateTime(2010, 11, 1, 0, 0, 0)
            where action.TimeOfAction < new DateTime(2011, 2, 7, 0, 0, 0)
            where action.EntityName == "seant"
            select action.TimeOfAction;
var count = query.Distinct().Count();

当然,如果您还需要query其他内容,则还需要保留原始版本:

DBEntities db = new DBEntities();
IQueryable<Action> query = 
            from action in db.Action
            where action.TimeOfAction > new DateTime(2010, 11, 1, 0, 0, 0)
            where action.TimeOfAction < new DateTime(2011, 2, 7, 0, 0, 0)
            where action.EntityName == "seant"
            select action;

var count = query.Select(x => x.TimeOfAction).Distinct().Count();
// Use query here as well to get at full action details

请注意,再次使用查询将产生第二个数据库查询。如果您需要计数与第二个查询所做的一致...或者从数据库中提取所有详细信息(使用ToList调用),您将需要查看事务方面的情况。然后在流程中执行Distinct部分。


返回自定义平等比较器...

假设TimeOfActionDateTime或其他具有合理哈希码的类型,您可以将您的类更改为:

class TimeComparer : IEqualityComparer<Action>
{
    public bool Equals(Action a, Action b)
    {
        return a.TimeOfAction == b.TimeOfAction;
    }

    public int GetHashCode(Action obj)
    {
        return obj.TimeOfAction.GetHashCode();
    }
}

请注意,我也简化了您的Equals方法 - 只要您发现自己:

if (condition)
{
    return true;
}
else
{
    return false;
}

您可以将其简化为:

return condition;

答案 1 :(得分:4)

首先,请注意不会在数据库服务器上运行,因此这与EF无关。

怀疑 部分,因为GetHashCodeEquals不一致;你理想情况下应该有:

public int GetHashCode(Action obj)
{
    return obj.TimeOfAction.GetHashCode();
}

因为那是你Equals所关心的。

但是,请注意,可以重写整个查询(如果使用IQueryable<Action> query(并删除AsEnumerable())而不是{{1,则可能会在数据库服务器上运行}})with:

IEnumerable<Action> query