Linq and Lambda's GroupBy method

Keywords: Database SQL linq

Group is often used in SQL. It is usually used to group one or more fields to find their sum, mean, etc.

The Groupby method in Linq also has this function. See the code for specific implementation:

Suppose there is a data set as follows:

public class StudentScore
{
public int ID { set; get; }
public string Name { set; get; }
public string Course { set; get; }
public int Score { set; get; }
public string Term { set; get; }
}


List<StudentScore> lst = new List<StudentScore>() 
{
new StudentScore(){ID=1,Name="Zhang San",Term="First semester",Course="Math",Score=80},
new StudentScore(){ID=1,Name="Zhang San",Term="First semester",Course="Chinese",Score=90},
new StudentScore(){ID=1,Name="Zhang San",Term="First semester",Course="English",Score=70},
new StudentScore(){ID=2,Name="Li Si",Term="First semester",Course="Math",Score=60},
new StudentScore(){ID=2,Name="Li Si",Term="First semester",Course="Chinese",Score=70},
new StudentScore(){ID=2,Name="Li Si",Term="First semester",Course="English",Score=30},
new StudentScore(){ID=3,Name="Wang Wu",Term="First semester",Course="Math",Score=100},
new StudentScore(){ID=3,Name="Wang Wu",Term="First semester",Course="Chinese",Score=80},
new StudentScore(){ID=3,Name="Wang Wu",Term="First semester",Course="English",Score=80},
new StudentScore(){ID=4,Name="Zhao Liu",Term="First semester",Course="Math",Score=90},
new StudentScore(){ID=4,Name="Zhao Liu",Term="First semester",Course="Chinese",Score=80},
new StudentScore(){ID=4,Name="Zhao Liu",Term="First semester",Course="English",Score=70},
new StudentScore(){ID=1,Name="Zhang San",Term="Second semester",Course="Math",Score=100},
new StudentScore(){ID=1,Name="Zhang San",Term="Second semester",Course="Chinese",Score=80},
new StudentScore(){ID=1,Name="Zhang San",Term="Second semester",Course="English",Score=70},
new StudentScore(){ID=2,Name="Li Si",Term="Second semester",Course="Math",Score=90},
new StudentScore(){ID=2,Name="Li Si",Term="Second semester",Course="Chinese",Score=50},
new StudentScore(){ID=2,Name="Li Si",Term="Second semester",Course="English",Score=80},
new StudentScore(){ID=3,Name="Wang Wu",Term="Second semester",Course="Math",Score=90},
new StudentScore(){ID=3,Name="Wang Wu",Term="Second semester",Course="Chinese",Score=70},
new StudentScore(){ID=3,Name="Wang Wu",Term="Second semester",Course="English",Score=80},
new StudentScore(){ID=4,Name="Zhao Liu",Term="Second semester",Course="Math",Score=70},
new StudentScore(){ID=4,Name="Zhao Liu",Term="Second semester",Course="Chinese",Score=60},
new StudentScore(){ID=4,Name="Zhao Liu",Term="Second semester",Course="English",Score=70},
};

You can think of this dataset as a two-dimensional table in the database.

Example 1

Usually, we will put the grouped data into anonymous objects, because the columns of the grouped data are not necessarily consistent with the original two-dimensional table. Of course, it is also possible to store the data in the original format, just use the corresponding type when select ing.

The first way is very simple, just group according to the following.

//Group, count the score of Sum according to the name, and put the statistical results in anonymous objects. There are two ways to write.
//The first way to write
Console.WriteLine("---------The first way to write");
var studentSumScore_1 = (from l in lst
group l by l.Name into grouped
orderby grouped.Sum(m => m.Score)
select new { Name = grouped.Key, Scores = grouped.Sum(m => m.Score) }).ToList();
foreach (var l in studentSumScore_1)
{
Console.WriteLine("{0}:Total score{1}", l.Name, l.Scores);
}

The second way of writing is actually equivalent to the first.

//The second way to write
Console.WriteLine("---------The second way to write");
var studentSumScore_2 = lst.GroupBy(m => m.Name)
.Select(k => new { Name = k.Key, Scores = k.Sum(l => l.Score) })
.OrderBy(m => m.Scores).ToList();
foreach (var l in studentSumScore_2)
{
Console.WriteLine("{0}:Total score{1}", l.Name, l.Scores);
}

Example 2

When the grouped fields are multiple, the multiple fields are usually combined into an anonymous object, and then group by the anonymous object.

Note: after group by, put the data into the variable grouped. Grouped is actually of iggrouping < tkey, Telement > type. Iggrouping < out tkey, out Telement > inherits IEnumerable < Telement >, and an additional attribute is Key. This Key is the Key word of the original grouping, that is, those fields with the same values. Here is the anonymous object. This Key can be obtained in the subsequent code to facilitate our programming.

When there are multiple fields in orderby, multiple fields are separated by commas in SQL, and several more orderby are written directly in Linq.

//Divide into groups, count the average scores of each subject according to two conditional semesters and courses, and put the statistical results in anonymous objects. There are two ways to write.
Console.WriteLine("---------The first way to write");
var TermAvgScore_1 = (from l in lst
group l by new { Term = l.Term, Course = l.Course } into grouped
orderby grouped.Average(m => m.Score) ascending
orderby grouped.Key.Term descending
select new { Term = grouped.Key.Term, Course = grouped.Key.Course, Scores = grouped.Average(m => m.Score) }).ToList();
foreach (var l in TermAvgScore_1)
{
Console.WriteLine("semester:{0},curriculum{1},Equal share{2}", l.Term, l.Course, l.Scores);
}
Console.WriteLine("---------The second way to write");
var TermAvgScore_2 = lst.GroupBy(m => new { Term = m.Term, Course = m.Course })
.Select(k => new { Term = k.Key.Term, Course = k.Key.Course, Scores = k.Average(m => m.Score) })
.OrderBy(l => l.Scores).OrderByDescending(l => l.Term);
foreach (var l in TermAvgScore_2)
{
Console.WriteLine("semester:{0},curriculum{1},Equal share{2}", l.Term, l.Course, l.Scores);
}

Example 3

There is no Having statement in SQL in Linq, so the where statement is used to filter the results after Group.

//Grouping, query with Having, query students with average score > 80
Console.WriteLine("---------The first way to write");
var AvgScoreGreater80_1 = (from l in lst
							group l by new { Name = l.Name, Term = l.Term } into grouped
							where grouped.Average(m => m.Score)>=80
							orderby grouped.Average(m => m.Score) descending
							select new { Name = grouped.Key.Name, Term = grouped.Key.Term, Scores = grouped.Average(m => m.Score) }).ToList();
foreach (var l in AvgScoreGreater80_1)
{
Console.WriteLine("full name:{0},semester{1},Equal share{2}", l.Name, l.Term, l.Scores);
}


Console.WriteLine("---------The second way to write");
//This method seems complicated. The first Groupby is to group multiple fields, so www.it165.net constructs an anonymous object,
//When grouping this anonymous object, the result is actually an ienumberable < iggrouping < anonymous type, studentscore > >.
//The Where method accepts and returns the same ienumberable < iggrouping < anonymous type, studentscore > > type,
//The type of Func delegate signed by the Where method becomes Func < igrouting < anonymous type, studentscore >, bool >,
//As mentioned earlier, igrouting < out tkey, out Telement > inherits IEnumerable < Telement >,
//Therefore, this type can have Average, Sum and other methods.

var AvgScoreGreater80_2 = lst.GroupBy(l => new { Name = l.Name, Term = l.Term })
.Where(m => m.Average(x => x.Score) >= 80)
.OrderByDescending(l=>l.Average(x=>x.Score))
.Select(l => new { Name = l.Key.Name, Term = l.Key.Term, Scores = l.Average(m => m.Score) }).ToList();
foreach (var l in AvgScoreGreater80_2)
{
Console.WriteLine("full name:{0},semester{1},Equal share{2}", l.Name, l.Term, l.Scores);
} 

Some lambda loop operations

 List<GoodsQuestionManageModel> list = JsonConvert.DeserializeObject<GoodsQuestionManageModel[]>(arrayList).ToList();
            //Repeat the judgment made by the foreground js
            list = list.Where(q => q.QuestionQuantity > 0).ToList();
            list.Where(q => q.GoodsQuestionTypeId == 0).ToList().ForEach(q => strMsg += string.Format("commodity Sku by{0}No question type selected for item<br>", q.Sku));
 
//Data judgment
IEnumerable<GoodsQuestionManageModel> listSum = list.Where(q => q.IsDelete == 0).GroupBy(q => new { q.Sku, q.GoodsName, q.GoodsQuantity,q.GoodsQuestionTypeId,q.TypeName })
                 .Select(q => new GoodsQuestionManageModel() { GoodsQuestionTypeId=q.Key.GoodsQuestionTypeId,TypeName=q.Key.TypeName,  Sku = q.Key.Sku, GoodsName = q.Key.GoodsName, GoodsQuantity = q.Key.GoodsQuantity, QuestionQuantity = q.Sum(t => t.QuestionQuantity) })
                 .Where(q => q.QuestionQuantity > q.GoodsQuantity);

listSum.ToList().ForEach(q => strMsg += string.Format("commodity Sku by{0},The problem type is[{1}]Number of commodity issues and{2}Greater than commodity quantity{3}<br>", q.Sku,q.TypeName, q.QuestionQuantity, q.GoodsQuantity));

[c#] GroupBy of LINQ

1, Prepare the class to use first:

1. Person class:

class Person
{
    public string Name { set; get; }
    public int Age { set; get; }
    public string Gender { set; get; }
    public override string ToString() => Name;
}

2. Prepare the List to use for grouping (GroupBy):

List<Person> personList = new List<Person>
{
    new Person
    {
        Name = "P1", Age = 18, Gender = "Male"

    },
    new Person
    {
        Name = "P2", Age = 19, Gender = "Male",
    },
    new Person
    {
        Name = "P2", Age = 17,Gender = "Female",
    }
};

2, First usage:

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);

Official interpretation: groups the elements in the sequence according to the specified key selector function.

The set we want to group is source, and the type of each element in the set is TSource. Here, the type of the first parameter keySelector is func < TSource, TKey >, which is used to group TSource elements according to the type TKey returned by this delegate, and the result is a grouped set (the set in the set).

Write the client test code as follows:

var groups = personList.GroupBy(p => p.Gender);
foreach (var group in groups)
{
    Console.WriteLine(group.Key);
    foreach(var person in group)
    {
        Console.WriteLine($"\t{person.Name},{person.Age}");
    }
}

The KeySelector specified in the above code is the Gender attribute of the Person class. Therefore, the above will be grouped according to Gender. We use two nested foreach loops to print the grouped contents to the console.

Because the type returned by groups is IEnumerable < igouping < tkey, tSource > >, the type returned above is IEnumerable < igouping < string, person > >.

IGouping < string, Person > is a grouped collection. The internal collection element is Person, and IGouping has a Key attribute of type string (referring to the Gender attribute type), which is used for grouping identification.

The output results are as follows:

The equivalent LINQ statement is:

var groups = from p in personList
             group p by p.Gender;

The above meaning can be understood as follows: take P from the personList, group P, use the grouping basis (Key) as p.Gender, store the grouping results in pGroup, and select and merge the grouping results into a set.

3, Second usage:

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer);

Official interpretation: group the elements in the sequence according to the specified key selector function, and compare the keys with the specified comparator.

This method has one more parameter than the first method, that is, an equality comparator. The purpose is that when TKey is a custom class, GroupBy can group according to the equality comparator according to the class specified by TKey,

Therefore, GroupBy does not know how to group user-defined classes. It needs to define its own equality comparator.

First, change the personList as follows (underlined part):

List<Person> personList = new List<Person>
{
    new Person
    {
        Name = "P1", Age = 18, Gender = "Male"

    },
    new Person
    {
        Name = "P1", Age = 19, Gender = "Male",
    },
    new Person
    {
        Name = "P3", Age = 17,Gender = "Female",
    }
};

Secondly, an equality comparator class is added to group people:

class PersonEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y) => x.Name == y.Name;
    public int GetHashCode(Person obj) => obj.Name.GetHashCode();
}

It defines how to define the equality of a Person, as long as iequalitycomparer < Person > is implemented. Here, Name is used as the basis for whether the Person class is the same.

Finally, let's group the Person class and write the client experiment code as follows:

var groups = personList.GroupBy(p => p, new PersonEqualityComparer());
foreach (var group in groups)
{
    Console.WriteLine(group.Key.ToString());
    foreach(var person in group)
    {
        Console.WriteLine($"\t{person.Age},{person.Gender}");
    }
}

The above grouping is based on the Person class and uses the self-defined Person class comparator. As long as the Name is the same, it is divided into a group,

The output results are as follows:

4, The third usage:

public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector);

Official interpretation: the elements in the sequence are grouped according to the specified key selector function, and the elements in each group are projected by using the specified function.

This is one more elementSelector than the first usage. The first usage is to group the collection itself according to TKey and add itself (TSource) to the group. The current usage can select the element type you want to add to the group.

Write the client experiment code as follows:

var groups = personList.GroupBy(p => p.Gender, p=>p.Name);
foreach (var group in groups)
{
    Console.WriteLine(group.Key.ToString());
    foreach(var name in group)
    {
        Console.WriteLine($"\t{name}");
    }
}

The above code is grouped according to p.Gender and takes p.Name as the element in the group.

The output results are as follows:

The equivalent LINQ statement is:

var groups = from p in personList
             group p.Name by p.Gender;

5, The fourth usage:

public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector);

Official interpretation: groups the elements in the sequence according to the specified key selector function, and creates the result value from each group and its key.

This is different from the previous usage. The previous usage is to group the results and return the igrouting < TKey, tSource > object. The current usage is to return the self-defined type (TResult). Before returning the self-defined type, two parameters will be passed in, one is TKey, the object specified during grouping, and the other is IEnumerable < tSource >, Is a collection of grouped internal objects.

Write the client experiment code as follows:

string GetPersonInfo(string gender, IEnumerable<Person> persons)
{
    string result = $"{gender}:\t";
    foreach (var p in persons)
    {
        result += $"{p.Name},{p.Age}\t";
    }
    return result;
}
var results = personList.GroupBy(p => p.Gender,(g, ps) => GetPersonInfo(g,ps));
foreach (var result in results)
{
    Console.WriteLine(result);
}

GetPersonInfo is a local method, which can be found in C#7.0 and above.

The above code outputs the grouped contents (one is TKey, which is p.Gender, the other is IEnumerable < tSource >, which is IEnumerable < person >) as a string. Therefore, the returned type is a string set.

The output results are as follows:

The equivalent LINQ statement is:

var results = from p in personList
              group p by p.Gender into pGroup
              select GetPersonInfo(pGroup.Key, pGroup);

6, The fifth usage:

public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer);

Official interpretation: groups the elements in the sequence according to the key selector function. The keys are compared by using a comparator, and the elements of each group are projected by using the specified function.

It is basically the same as the third usage, except that an equality comparator is added for the basis of grouping.

Using the personList and personequality comparer of the second usage, write the client experiment code as follows:

var groups = personList.GroupBy(p => p, p => new { p.Age,p.Gender },new PersonEqualityComparer());
foreach (var group in groups)
{
    Console.WriteLine(group.Key.ToString());
    foreach (var name in group)
    {
        Console.WriteLine($"\t{name.Age},{name.Gender}");
    }
}

The grouping basis of the above code is Person, and the Person equality comparer is used as the comparator of Person grouping. Each group is an anonymous type collection.

The output results are as follows:

7, Sixth usage:

public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector, IEqualityComparer<TKey> comparer);

Official interpretation: groups the elements in the sequence according to the specified key selector function, and creates the result value from each group and its key. Compares keys by using the specified comparator.

It is basically the same as the fourth usage, except that an equality comparator is added for the basis of grouping.

Using the personList and personequality comparer of the second usage, write the client experiment code as follows:

string GetPersonInfo(Person person, IEnumerable<Person> persons)
{
    string result = $"{person.ToString()}:\t";
    foreach (var p in persons)
    {
        result += $"{p.Age},{p.Gender}\t";
    }
    return result;
}
var results = personList.GroupBy(p => p, (p, ps) => GetPersonInfo(p, ps),new PersonEqualityComparer());
foreach (var result in results)
{
    Console.WriteLine(result);
}

The grouping basis of the above code is Person, and the Person equality comparer is used as the comparator of Person grouping. There is a Person set in each group, and the string with the return type of string is output.

The output results are as follows:

8, Seventh usage:

public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector);

Official interpretation: groups the elements in the sequence according to the specified key selector function, and creates the result value from each group and its key. Projects the elements of each group by using the specified function.

Similar to the fourth method, it only selects the elements in the group. It was originally TSource, but now it is TElement.

Write the client experiment code as follows:

string GetPersonInfo(string gender, IEnumerable<string> names)
{
    string result = $"{gender}:\t";
    foreach (var name in names)
    {
        result += $"{name}\t";
    }
    return result;
}
var results = personList.GroupBy(p => p.Gender, (p=>p.Name) ,(g, ns) => GetPersonInfo(g, ns));
foreach (var result in results)
{
    Console.WriteLine(result);
}

The above code will use Gender to group, combine the grouped information into a string, and output it to the console.

The output results are as follows:

9, The eighth usage:

public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer);

Official interpretation: groups the elements in the sequence according to the specified key selector function, and creates the result value from each group and its key. The key values are compared by using the specified comparator, and the elements of each group are projected by using the specified function.

It is basically the same as the seventh usage, except that an equality comparator is added for the basis of grouping.

Using the personList and personequality comparer of the second usage, write the client experiment code as follows:

var results = personList.GroupBy(p => p, (p=>new { p.Age,p.Gender}),
    (p, ns) => 
    {
        string result = $"{p.ToString()}:\t";
        foreach (var n in ns)
        {
            result += $"{n.Age},{p.Gender}\t";
        }
        return result;
    },new PersonEqualityComparer());
foreach (var result in results)
{
    Console.WriteLine(result);
}

The above code will use Person grouping, use Person comparator as the basis for grouping, and combine the grouped information into a string and output it to the console.

The output results are as follows:

Posted by mlschutz on Sun, 03 Oct 2021 17:31:13 -0700