m.edmondson
m.edmondson

Reputation: 30922

GroupBy on multiple properties with complex type

I have written a GroupBy statement like so:

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

In the grouping is Address which is a complex type:

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

However this does not correctly group by address, instead returning a separate group for each element.

It was my understanding that to group by the complex type you must provide an IEqualityComparer so I created the following:

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

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

and supplied it like so (subset of above):

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

This however gives me

The type arguments cannot be inferred from the usage. Try specifying the type arguments explicitly.

I'm at a loss as to my next step, surely this sort of grouping shouldn't be too difficult?

Upvotes: 2

Views: 1061

Answers (2)

PinBack
PinBack

Reputation: 2574

You can try to override Equals and GetHashCode in your Address class (like in your comparer):

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

Upvotes: 1

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236278

Enumerable.GroupBy allows you to pass custom equality comparer for keys. But in your case key is not an Address object - it's an anonymous object containing three properties - SiteRefNum, SiteRefName and Address. Of course passing AddressComparer to compare such keys will cause an error.

And your first problem was using complex object as key property. If you don't override Equals and GetHashCode methods for Address objects, then all addresses will be compared by reference. Which is of course different for each address instance. You can provide Equals and GetHashCode implementations to compare addresses.

Or you can modify your query to use address string for grouping:

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

You can use method syntax for query, but I find declarative query syntax more readable :)

Upvotes: 5

Related Questions