Preamble
LINQ is a modern query language which presents a simple, yet powerful way to query data-collections in a variety of sources. It offers a consistent model for working with data from a variety of sources (XML documents, SQL databases, ADO.NET databases, .NET collections etc.)
As an alternative to the “standard” query syntax, LINQ can also be used with a method based syntax and complementary lambda operators - shortening query length and offering a concise expression form.
Motivation
LINQ presents an opportunity to manipulate data from within a .NET based environment - raising the bar for software-based data-manipulation and querying. In addition it reinforces learning-concepts and interaction modalities within the C# language.
Quick Comparison: Query vs. Method
A quick comparison of both syntax types - finding odd numbers in an array.
In both cases n
acts as the range (parameter).
To the right side of the
()
the method declaration acts as the in-place function.
// 1.
int[] numbers = { 5, 6, 3, 2, 1, 5, 6, 7, 8, 4, 234, 54, 14, 653, 3, 4, 5, 6, 7 };
var oddQuery = from n in numbers
where n % 2 == 1
select n;
Console.WriteLine(string.Join(", ", oddQuery));
5, 3, 1, 5, 7, 653, 3, 5, 7
var oddSyntax = numbers.Where(n => (n % 2 ==1))
Console.WriteLine(string.Join(", ", oddSyntax));
5, 3, 1, 5, 7, 653, 3, 5, 7
Combination of Where and Select
The Where
and Select
methods are demonstrated below.
// 2.
List<Warrior> warriors = new List<Warrior>()
{
new Warrior() {Height = 180},
new Warrior() {Height = 190},
new Warrior() {Height = 160},
new Warrior() {Height = 140},
new Warrior() {Height = 185},
new Warrior() {Height = 170},
};
.....
..........
..............
private internal Warrior
{
int Height {get; set;}
}
var shortWarriors = Warriors.Where(wh => (wh.Height <= 150))
.Select(wh => wh.Height);
Console.WriteLine(string.Join(", ",shortWarriors));
140
Grouping and MultKey Grouping
Grouping is easily implemented. Using the same list of people
as in previous examples.
// 3.
var simpleGrouping = people.GroupBy(p => p.Gender)
foreach(var item in simpleGrouping)
{
Console.WriteLine($"Gender:{item.Key}");
foreach(var p in item)
{
Console.WriteLine($" Name: {p.Name}, Age: {p.Age}");
}
}
Male
Name: Tod, Age: 25
Name: John, Age: 25
Name: Kyle, Age: 21
Name: Neil, Age: 35
Name: Paul, Age: 36
Female
Name: Anna, Age: 23
Name: Anna, Age: 32
Name: Maria, Age: 23
Name: Joan, Age: 28
Name: Stacey, Age: 23
Grouping with multiple keys is just as digestible.
// 4.
var multiKeyGrouping = people.GroupBy(p => new {p.Gender}, {p.Age})
foreach(var item in multiKeyGrouping)
{
Console.WriteLine($"{item.Key.Gender}, {item.Key.Age}");
foreaach(var p in item)
{
ConsoleWriteLine($"{p.FirstName}, {p.Height}");
}
}
Male, 25
Name: Tod, Height: 180
Name: John, Height: 170
Female, 23
Name: Anna, Height: 150
Name: Maria, Height: 160
Name: Stacey, Height: 170
Male, 21
Name: Kyle, Height: 164
Female, 32
Name: Anna, Height: 164
Female, 28
Name: Joan, Height: 167
Male, 35
Name: Neil, Height: 195
Male, 36
Name: Paul, Height: 190
Ordering can be further chained onto the query (in the example below, ordering the results by the membership count per group in ascending order).
// 5.
var orderedMultiKeyGrouping = people.GroupBy(p => new {p.Gender, p.Age}).OrderBy(p => p.Count())
.....
..........
.............
Grouping by Custom Keys
Custom Keys can be defined by using the ternary operator within the query.
// 5.
var cusEvenOdd = numbers.OrderBy(n => n)
.GroupBy(n => (n % 2 == 0) ? "Even" : "Odd")
.OrderBy(n => n.Count());
foreach(var item in cusEvenOdd)
{
Console.WriteLine($"{item.Key}");
foreach(var n in item)
{
Console.WriteLine($"{n}");
}
}
Odd
1
3
3
5
5
5
7
7
653
Even
2
4
4
6
6
6
8
14
54
234
The conditions and ternary operators can be arranged within a Anonymous Method for readability.
// 6.
var cusAgeGroups = people.GroupBy(p=>
{
var young = p.Age < 25;
var adult = p.Age > 30;
var age = young ? "Young" : adult ? "Adult" : Senior;
return age;
});
foreach(var item in cusAgeGroups)
{
Console.WriteLine($" {item.Key}");
foreach(var p in item)
{
Console.WriteLine($"{p.FirstName}, {p.Age}");
}
}
Senior
Tod, 25
John, 25
Joan, 28
Young
Anna, 23
Kyle, 21
Maria, 23
Stacey, 23
Adult
Anna, 32
Neil, 35
Paul, 36
Selections of Group Membership
Occasionally its very useful to just retrieve membership-counts of the various groups.
// 7.
var howManyInGroup = people.GroupBy(p => p.Gender)
.Select (g => new
{
Gender = g.Key,
NumOfPeople = g.Count()
});
foreach( var item in howManyInGroup)
{
Console.WriteLine($"{item.Gender}");
Console.WriteLine($"{item.NumOfPeople}");
}
Male
5
Female
5
Inner Joins
It helps to be familiar with the Query Syntax approach before starting here - but in VSCode/2019 you also get Intelli-Sense to determine the method arguments.
// 8.
var innerJoinMS = suppliers.Join(buyers, s => s.District, b => b.District,
(s, b)=> new
{
SupplierName = s.Name,
BuyerName = b.Name,
District = s.District
});
foreach(var item in innerJoinMS)
{
Console.WriteLine($"District: {item.District}, Supplier Name: {item.Supplier}, Buyer Name: {item.BuyerName}");
}
District: Point Break, Supplier Name: Andy, Buyer Name: John
District: West Side, Supplier Name: Tod, Buyer Name: Paul
District: West Side, Supplier Name: Tod, Buyer Name: Angie
District: West Side, Supplier Name: Ian, Buyer Name: Paul
District: West Side, Supplier Name: Ian, Buyer Name: Angie
District: South, Supplier Name: Anna, Buyer Name: Joan
District: South, Supplier Name: Anna, Buyer Name: George
District: Central, Supplier Name: Jerry, Buyer Name: Flo
District: Central, Supplier Name: Jerry, Buyer Name: Mary
District: Central, Supplier Name: Paul, Buyer Name: Flo
District: Central, Supplier Name: Paul, Buyer Name: Mary
Composite Join
Composite Joins just matches across >=2 keys between the two collections.
The keys must be stored in their own Anonymous objects (in in a comparable way to the Query approach).
// 9.
var compositeJoinMS = suppliers.Join(buyers,
s=> new {s.District, s.Age},
b => new{b.Disrict, s.Age},
(s, b) => new
{
SupplierName = s.Name,
BuyerName = b.Name,
District = s.District,
Age = s.Age
});
foreach(var item in compositeJoinMS)
{
Console.WriteLine($"{item.District}, {item.Age}");
Console.WriteLine($" Supplier: {item.SupplierName}");
Console.WriteLine($" Buyer: {item.BuyerName}");
}
Point Break, 25
Supplier: Andy
Buyer: John
West Side, 35
Supplier: Tod
Buyer: Paul
South, 30
Supplier: Anna
Buyer: Joan
Central, 28
Supplier: Jerry
Buyer: Mary