GroupBy在复杂类型

时间:2017-03-07 14:12:51

标签: c# .net linq group-by

我写了一个像这样的GroupBy语句:

var aggregated = sitesWithLive
    .GroupBy(s => new {s.SiteRefNum, s.SiteRefName, s.Address})
    .Select(g =>
        new Site
        {
            SiteRefNum = g.Key.SiteRefNum,
            SiteRefName = g.Key.SiteRefName,
            Address = g.Key.Address,
            ContractLive = g.Max(x => x.ContractLive)
        });

在分组中Address是一个复杂的类型:

public class Address
{
    public string Name { get; set; }
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string Line3 { get; set; }
    public string Line4 { get; set; }
    public string PostCode { get; set; }

    public bool IsEmpty()
    {
        return GetType().GetProperties()
            .Where(a => a.GetValue(this) is string)
            .Select(a => (string)a.GetValue(this))
            .All(string.IsNullOrEmpty);
    }

    public override string ToString()
    {
        var addr = Line1 + "," + Line2 + "," + Line3 + "," + Line4 + "," + PostCode;
        var address = Regex.Replace(addr, @"^,+|,{2,}|,(?=[\w.])", ", ");
        return address;
    }
}

但是,这并没有按地址正确分组,而是为每个元素返回一个单独的组。

我的理解是,要按复杂类型进行分组,您必须提供IEqualityComparer,因此我创建了以下内容:

public class AddressComparer : IEqualityComparer<Address>
{
    public bool Equals(Address x, Address y)
    {
        return x.ToString() == y.ToString();
    }

    public int GetHashCode(Address obj)
    {
        return 1;
    }
}

并提供它(如上所述):

var aggregated = sitesWithLive.GroupBy(s => new {s.SiteRefNum, s.SiteRefName, s.Address}, new AddressComparer())
...

然而,这给了我

  

无法从用法推断出类型参数。尝试指定   显式的类型参数。

我对下一步感到茫然,这种分组肯定不会太难吗?

2 个答案:

答案 0 :(得分:4)

Enumerable.GroupBy允许您为键传递自定义相等比较器。但在您的情况下,密钥不是Address对象 - 它是一个包含三个属性的匿名对象 - SiteRefNumSiteRefNameAddress。当然,通过AddressComparer比较这些键会导致错误。

你的第一个问题是使用复杂的对象作为关键属性。如果您不覆盖Equals个对象的GetHashCodeAddress方法,则会通过引用比较所有地址。对于每个地址实例,这当然是不同的。您可以提供EqualsGetHashCode实施来比较地址。

或者您可以修改查询以使用地址字符串进行分组:

var aggregated = 
    from s in sitesWithLive
    group s by new {
       s.SiteRefNum,
       s.SiteRefName,
       Address = s.Address.ToString() // here we group by string
    } into g
    select new Site
    {
        SiteRefNum = g.Key.SiteRefNum,
        SiteRefName = g.Key.SiteRefName,
        Address = g.First().Address, // here we just get first address object
        ContractLive = g.Max(x => x.ContractLive)
    };

您可以使用方法语法进行查询,但我发现声明性查询语法更具可读性:)

答案 1 :(得分:1)

您可以尝试在Address类中覆盖Equals和GetHashCode(就像在比较器中一样):

public override bool Equals(object obj)
{
    Address adr = obj as Address;
    if (adr != null)
        return adr.ToString() == this.ToString();
    return false;
}

public override int GetHashCode()
{
    return this.ToString().GetHashCode();
}