Data Model Preparation
Before demonstrating the query operators, we define two reference classes to serve as our data source. These models represent workforce records and their corresponding performance metrics.
public class Staff
{
public string RecordId { get; set; }
public string FullName { get; set; }
public int YearsOnJob { get; set; }
public Staff(string recordId, string fullName, int yearsOnJob)
{
RecordId = recordId;
FullName = fullName;
YearsOnJob = yearsOnJob;
}
public static IEnumerable<Staff> FetchAllRecords()
{
return new List<Staff>
{
new("R01", "Alice Chen", 25),
new("R02", "Bob Smith", 24),
new("R03", "Charlie Lee", 23),
new("R04", "David Wang", 26),
new("R05", "Eva Zhang", 27),
new("R06", "Alice Chen", 25)
};
}
}
public class PerformanceLog
{
public string RecordId { get; set; }
public double CodingScore { get; set; }
public double DesignScore { get; set; }
public double CommunicationScore { get; set; }
public PerformanceLog(string recordId, double codingScore, double designScore, double communicationScore)
{
RecordId = recordId;
CodingScore = codingScore;
DesignScore = designScore;
CommunicationScore = communicationScore;
}
public static IEnumerable<PerformanceLog> FetchMetrics()
{
return new List<PerformanceLog>
{
new("R01", 85, 90, 85),
new("R02", 85, 90, 85),
new("R03", 60, 70, 65),
new("R04", 59, 99, 75),
new("R05", 66, 90, 65)
};
}
}
Filtering and Type Narrowing
The Where operator applies a Boolean expression to each element. It can accept an index-based lambda when element position matters. The OfType<T> operator filters sequences containing mixed types, retaining only those that match the specified generic parameter.
// Filter by exact match and evaluate with index
var staffMatches = Staff.FetchAllRecords().Where((member, idx) => member.RecordId == "R01");
foreach (var m in staffMatches)
{
Console.WriteLine($"ID: {m.RecordId} | Name: {m.FullName}");
}
// Numeric sequence filtered by value and index parity
int[] rawNumbers = { 1, 3, 4, 6, 8, 7, 9, 1, 3, 2 };
var evenAtOddIndex = rawNumbers.Where((val, index) => val % 2 == 0 && index % 2 == 1);
object[] mixedBag = { "alpha", 12, 34, "beta", 56 };
// Retains only integers, safely discarding strings
IEnumerable<int> numbersOnly = mixedBag.OfType<int>();
Sorting and Reversing Sequences
Sequences can be ordered ascending or descending using OrderBy and OrderByDescending. Multiple sort criteria are chained via ThenBy or ThenByDescending. To invert the final result, append Reverse().
// Single criterion sort
var sortedScores = rawNumbers.OrderByDescending(x => x);
// Multi-condition ordering applied to objects
var orderedStaff = Staff.FetchAllRecords()
.OrderByDescending(s => s.RecordId)
.ThenBy(s => s.FullName);
// Flipping the previously sorted sequence
var reversedOrder = Staff.FetchAllRecords()
.OrderByDescending(s => s.RecordId)
.ThenBy(s => s.FullName)
.Reverse();
Data Grouping
Grouping collects elements under common keys. This is useful for identifying duplicates or preparing aggregates. The following query groups personnel by combined name and tenure, filtering for groups exceeding a single occurrence.
var duplicateProfiles = from member in Staff.FetchAllRecords()
group member by new { member.FullName, member.YearsOnJob } into grp
where grp.Count() > 1
select new { grp.Key.FullName, grp.Key.YearsOnJob };
foreach (var entry in duplicateProfiles)
{
Console.WriteLine($"Name: {entry.FullName}, Age: {entry.YearsOnJob}");
}
Combining Collections via Joins
LINQ supports relational-style joins. An inner join returns matched pairs only. A left outer join preserves unmatched elements from the left collection by pairing them with a default value using DefaultIfEmpty().
// Inner Join: Only staff with matching performance logs appear
var innerJoined = from emp in Staff.FetchAllRecords()
join perf in PerformanceLog.FetchMetrics() on emp.RecordId equals perf.RecordId
select new
{
emp.RecordId,
emp.FullName,
Coding = perf.CodingScore,
Design = perf.DesignScore,
Comm = perf.CommunicationScore
};
// Left Outer Join: All staff retained, missing scores fallback to zero
var leftOuterJoined = from emp in Staff.FetchAllRecords()
join perf in PerformanceLog.FetchMetrics() on emp.RecordId equals perf.RecordId into tempJoin
from record in tempJoin.DefaultIfEmpty()
select new
{
RecordId = emp.RecordId,
FullName = emp.FullName,
Coding = record?.CodingScore ?? 0.0,
Design = record?.DesignScore ?? 0.0,
Comm = record?.CommunicationScore ?? 0.0
};
Set Operations
Standard mathematical set operators compare two collections and return a new sequence. These are highly efficient for deduplication, comparison, and combination tasks.
int[] firstSet = { 1, 3, 5, 2 };
int[] secondSet = { 1, 2, 6, 7 };
// Elements present in both collections
var intersection = firstSet.Intersect(secondSet);
// Elements unique to the first collection
var difference = firstSet.Except(secondSet);
// Combined elements with automatic uniqueness
var unionResult = firstSet.Union(secondSet);
Sequence Partitioning and Paging
The Skip and Take operators enable virtual pagination by offsetting and limiting results. This avoids loading entire datasets into memory.
int pageSize = 5;
int totalItems = Staff.FetchAllRecords().Count();
int totalPages = (int)Math.Ceiling(totalItems / (double)pageSize);
for (int pageIndex = 0; pageIndex < totalPages; pageIndex++)
{
Console.WriteLine($"--- Page {pageIndex + 1} ---");
var pageData = Staff.FetchAllRecords()
.OrderBy(x => x.YearsOnJob)
.Skip(pageIndex * pageSize)
.Take(pageSize);
foreach (var item in pageData)
{
Console.WriteLine($"[{item.RecordId}] {item.FullName} ({item.YearsOnJob} yrs)");
}
}
Quantifier Operators
Quantifiers return boolean values evaluating conditions across entire sequences. Any checks existence, All verifies universal compliance, and Contains checks direct membership.
bool hasMatchingProfile = Staff.FetchAllRecords()
.Any(emp => emp.FullName == "Alice Chen" && emp.YearsOnJob == 25);
bool everyoneIsSenior = Staff.FetchAllRecords()
.All(emp => emp.YearsOnJob >= 30); // Returns false based on sample data
Staff targetPerson = new Staff("R01", "Alice Chen", 25);
bool existsInCollection = Staff.FetchAllRecords().Contains(targetPerson);
Console.WriteLine($"Exists: {hasMatchingProfile}, Universal Seniority: {everyoneIsSenior}, Direct Match: {existsInCollection}");
Aggregation Functions
LINQ provides built-in mathematical reducers that traverse a sequence and produce a single scalar value or a combined result. Common methods include:
Count()/LongCount(): Determines sequence length or conditionally counts elements.Sum(): Calculates the total of numeric projections.Min()/Max(): Extracts boundary values from comparable sequences.Average(): Computes the arithmetic mean.Aggregate(): Applies a custom accumulator functon to fold a sequence into a single result (e.g., concatenating strings, building dictionaries, or calculating running totals).
var metrics = PerformanceLog.FetchMetrics();
int logCount = metrics.Count();
double avgCoding = metrics.Average(m => m.CodingScore);
double topDesign = metrics.Max(m => m.DesignScore);
// Custom folding example: concatenate names separated by semicolons
string concatenatedNames = Staff.FetchAllRecords()
.Select(e => e.FullName)
.Aggregate((acc, curr) => $"{acc} | {curr}");