Language Integrated Query or LINQ is a C# feature that allows you to query different data sources using a unified language syntax.
In the first part we learned what is LINQ, when it is used and went through every-day operations. You can learn all about it in the article below:
In this blog we're moving forward with LINQ:
- Set Operations
- Quantifiers
- Aggregation
Once again, we'll be working a List<Student>
imported from JSON. Here, you can aquire the data as well as learn how to import it into C#.
SET OPERATIONS
LINQ Provides set operators to perform mathematical set operations on sequences or collections.
Distinct
The Distinct
operator is used to filter out (remove) the duplicate elements from the collection:
IEnumerable<string> countries = students.Select(s => s.Country );
// ["Bosnia", "Bosnia", "UK", "Turkey", "India", "Turkey"...]
IEnumerable<string> distinctCountries = students.Select(s => s.Country).Distinct();
// ["Bosnia", "UK", "Turkey", "India", "Egypt"...]
DistinctBy
The DistinctBy
operator takes a predicate that specifies the condition for the distinction:
IEnumerable<Student> distinctStudents = students.DistinctBy(s => s.Country)
// collection of students from distinct countries
This can be applied to a collection of countries as well:
var distinctCountries = students.DistinctBy(s => s.Country).Select(s => s.Country);
// unique countries
Union
The Union
operator joins multiple collections into one leaves out the duplicate values. To make things more interesting I created another collection of students that we'll use alongside the original:
var newStudents = new List<Student>
{
new Student() { ID = 11, Name = "Anz", Age = 19, Country = "Australia" },
new Student() { ID = 12, Name = "Mohamed", Age = 23, Country = "Egypt" },
new Student() { ID = 13, Name = "Amar", Age = 24, Country = "Bosnia" }
};
Let's say we want to take countries from both collections and create union of the two. Here's how that'd be done:
var originalStudentsCountries = students.Select(s => s.Country);
var newStudentsCountries = newStudents.Select(s => s.Country);
var countriesUnion = originalStudentsCountries.Union(newStudentsCountries);
As we can, the new collection is combination of the two other collections without duplicate entries.
UnionBy
The UnionBy
speeds things up by reducing the number of steps needed to make the union.
var countriesUnion2 = students.UnionBy(newStudents, s => s.Country);
// the same result as above
Intersect
The Intersect
operator is used to compare two collections and find the elements that are present in both sets. We'll once again compare the countries in two collections and inspect the result:
var originalStudentsCountries = students.Select(s => s.Country);
var newStudentsCountries = newStudents.Select(s => s.Country);
var countriesIntersection = originalStudentsCountries.Intersect(newStudentsCountries);
// ["Bosnia", "Egypt"]
The outcome is the distinct list of countries that are present in both collections.
IntersectBy
The logic can once again be simplified by applying the IntersectBy
operator:
var countriesIntersection2 = students
.IntersectBy(newStudents.Select(ns => ns.Country), s => s.Country);
// the same result as above
Except
The Except
operator returns a combination of elements that are present in one collection, but not in the other. Basically the opposite of Intersect
.
var originalStudentsCountries = students.Select(s => s.Country);
var newStudentsCountries = newStudents.Select(s => s.Country);
var countriesException = originalStudentsCountries.Except(newStudentsCountries);
The output is the list of countries present in the first, but not present in the second collection.
ExceptBy
The ExceptBy
achieves the same as Expect
with less steps:
var countriesException2 = students
.ExceptBy(newStudents.Select(ns => ns.Country), s => s.Country);
// the same result as above
QUANTIFIERS
Contains
The Contains
operator is used to validate if a specific item exists in the collection. For example, we can confirm that we have at least one student from Bosnia:
bool existingStudent = students.Select(s => s.Country).Contains("Bosnia");
// true
But what we don't have is a student that is 100 years old.
bool nonExistingStudent = students.Select(s => s.Age).Contains(100);
// false
Any
Similarly to Contains
, the Any
operator is used to confirm if there is at least one student that meets specified criteria.
bool atLeastOneFromUK = students.Any(s => s.Country == "UK");
// true
All
The All
operator is used to verify if all elements in the collection meet specified criteria:
bool allEighteenOrAbove = students.All(s => s.Age >= 18);
// true
AGGREGATION
Count
The Count
is used to to add together all the elements in a collection:
int totalStudentsCount = students.Count();
Console.WriteLine(totalStudentsCount); // 10
int studentsFromTurkeyCount = students.Where(s => s.Country == "Turkey").Count();
Console.WriteLine(studentsFromTurkeyCount); // 2
Min, Max, Average
These operators are used to determine the minimum, maximum and average element in the collection.
var ages = students.Select(s => s.Age);
var youngestAge = ages.Min(); // 18
var oldestAge = ages.Max(); // 24
var averageAge = ages.Average(); // 19.89
Sum
The Sum
operator is used to aggregate all elements in the collections:
var ages = students.Select(s => s.Age);
int agesSum = ages.Sum();
Console.WriteLine(agesSum); // 199
GroupBy
The GroupBy
operator is used to group items in the collection by one or more properties. For example, earlier we learned that we can retrieve a list of unique countries:
var countries = students
.Select(s => s.Country)
.Distinct();
The same can be achieved using the GroupBy
operator.
var countries2 = students
.GroupBy(s => s.Country)
.Select(group => group.Key);
-
GroupBy
groups by specified properties (Country) -
Select
is used to read values out of groupped collection -
group
is a groupped collection (IEnumerable<IGroupping<T>>
) - The
Key
is the groupping property (Country)
The end result is the same as above.
Here is another example of using Groupby
operator to calculate the occurrences of each country in the collection:
var countryOccurrences = students
.GroupBy(s => s.Country)
.Select(g => new { Country = g.Key, Occurrences = g.Count() });
Since the groupped collection (g
) is an IEnumerable
, we can call any of the LINQ operators (in this case Count
).
Lastly, we can group the students by their age in dictionary structure, like:
{
18: [List of students that are 18 years old],
19: [List of students that are 19 years old],
...
}
var studentsGrouppedByAge = students
.GroupBy(s => s.Age) // group by age
.ToDictionary(g => g.Key, g => g.ToList() // form a dictionary
.Select(s => new { Name = s.Name, Country = s.Country })); // select specific properties
Furthermore, we can print each group and each student by using a combination of foreach
loops:
foreach (var studentsGroup in studentsGrouppedByAge)
{
foreach (var student in studentsGroup.Value) // we use .Value because studentsGroup is a dictionary
{
Console.WriteLine($"Name: {student.Name}, Country: {student.Country}");
}
}
Other Chapters:
In the coming parts we'll dive deeper into LINQ.
Don't forget to hit the follow button. Also, follow me on Twitter to stay up to date with my upcoming content.
Bye for now 👋
Top comments (5)
While I think learning by doing is very useful, I learned LINQ through LINQPad back in the day. Refreshing article!
I didn't know about LINQPad. Thanks for sharing!
Will peek into it
You'll like it!
Hi Mirza Leka,
Top, very nice and helpful !
Thanks for sharing.
Anytime!