Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested GroupBy LINQ Using Fluent Syntax

Tags:

linq

group-by

I am trying to write a nested GroupBy LINQ expression using the fluent (i.e., "method") syntax.

Here's my class and data:

class Person
{
    public String ZipCode, Gender, Name;
}

private static List<Person> people = new List<Person>
{
    new Person { ZipCode= "11111", Gender = "M", Name = "Tom" },
    new Person { ZipCode= "11111", Gender = "M", Name = "Bob" },
    new Person { ZipCode= "11111", Gender = "F", Name = "Nancy" },
    new Person { ZipCode= "11111", Gender = "F", Name = "Lisa" },
    new Person { ZipCode= "22222", Gender = "M", Name = "Dan" },
    new Person { ZipCode= "33333", Gender = "F", Name = "Mary" },
    new Person { ZipCode= "44444", Gender = "F", Name = "Joan" },
    new Person { ZipCode= "44444", Gender = "F", Name = "Jane" },
    new Person { ZipCode= "44444", Gender = "M", Name = "Bill" }
};

What I'm wanting to achieve is an object that is grouped by ZipCode and then by Gender within ZipCode. In terms of an object type, I am looking for this:

IEnumerable<IGrouping<string, IEnumerable<IGrouping<string, Person>>>>

This would then allow me to access the query results like this:

foreach(var byZip in mainQuery) {
   Console.WriteLine(byZip.Key);  // print the ZipCode
   foreach(var byGender in byZip) {
      Console.WriteLine(byGender.Key) // print the Gender
      foreach (Person p in byGender)
         Console.WriteLine(p.Name);
   }
}

Again, I'm looking to use fluent notation.


Based on Jeff's answer below, this is the query and the access:

IEnumerable<IGrouping<string, IEnumerable<IGrouping<string, Person>>>> query = 
    people
        .GroupBy(p => p.ZipCode)
            .GroupBy(
                keySelector: g => g.Key,
                elementSelector: g => g.GroupBy(p => p.Gender)
            );


foreach (IGrouping<string, IEnumerable<IGrouping<string, Person>>> byZip in query)
{
    Console.WriteLine(byZip.Key);  // print the ZipCode
    foreach (IEnumerable<IGrouping<string, Person>> byGender in byZip)
    {
        foreach (IGrouping<string, Person> t in byGender)
        { 
            Console.WriteLine(t.Key); // print the Gender
            foreach (Person y in t)
                Console.WriteLine(y.Name);
        }

    }
}
like image 395
Tom Baxter Avatar asked Jan 06 '23 12:01

Tom Baxter


2 Answers

In query syntax, you could write it like this:

var query =
    from p in people
    group p by p.ZipCode into g
    let gender =
        from p in g
        group p by p.Gender
    group gender by g.Key;

And converting it using the "fluent" syntax, that would become this:

var query2 = people.GroupBy(p => p.ZipCode)
    .GroupBy(
        g => g.Key,
        g => g.GroupBy(p => p.Gender)
    );

However the way you want to access this doesn't match the actual type. Your access pattern makes your type actually be:

IEnumerable<IGrouping<string, IGrouping<string, Person>>>

So the query should be:

var query =
    from p in people
    group p by p.ZipCode into g
    let gender =
        from p in g
        group p by p.Gender
    from g2 in gender
    group g2 by g.Key;

And the conversion... is a bit more complicated.

var query2 = people.GroupBy(p => p.ZipCode)
    .SelectMany(g =>
        g.GroupBy(p => p.Gender).GroupBy(g2 => g.Key)
    );
like image 125
Jeff Mercado Avatar answered Jan 13 '23 21:01

Jeff Mercado


The following:

var peopleGroupedByZipCode = people
    .GroupBy(p => p.ZipCode)
    .Select(group => new
    {
        ZipCode = group.Key,
        PeopleGroupedByGender = group.GroupBy(p => p.Gender)
    });

foreach (var resultsByZipCode in peopleGroupedByZipCode)
{
    Console.WriteLine("ZipCode: " + resultsByZipCode.ZipCode);
    foreach (var resultsByGender in resultsByZipCode.PeopleGroupedByGender)
    {
        Console.WriteLine(" Gender : " + resultsByGender.Key);
        foreach (var resultWithinGenderGroup in resultsByGender)
            Console.WriteLine("  " + resultWithinGenderGroup.Name);

    }
}

will write out the following:

ZipCode: 11111

Gender : M

Tom

Bob

Gender : F

Nancy

Lisa

ZipCode: 22222

Gender : M

Dan

ZipCode: 33333

Gender : F

Mary

ZipCode: 44444

Gender : F

Joan

Jane

Gender : M

Bill

Is that the sort of thing that you want?

(I realise that it's not precisely an IEnumerable<IGrouping<string, IEnumerable<IGrouping<string, Person>>>> but I figured that it was more the shape of the data that you were interested in that a precise type).

like image 22
Dan Roberts Avatar answered Jan 13 '23 21:01

Dan Roberts