LINQ 筛选、排序、分组与联接完整示例

作者:袖梨 2026-09-21

C# 中用 LINQ 处理数据,可按“数据源 → Where 筛选 → OrderBy/ThenBy 排序 → GroupBy 分组聚合 → Join 关联”的思路组合操作。这些操作步骤不必每次全部采用;先明确最终应当什么形状的结果,再选操作符,能避免为了“用 LINQ”而制造不必要的中间集合。

Microsoft C# 文档中的 LINQ 查询表达式基础

Microsoft C# 文档将查询定义为描述从数据源取回何种数据的指令集;查询变量和实际执行时点并不总是同一件事。

准备一组贯穿数据

record Department(int Id, string Name);
record Employee(int Id, string Name, int DepartmentId, decimal Salary, bool Active);

var departments = new[]
{
    new Department(1, "Engineering"),
    new Department(2, "Support"),
    new Department(3, "Sales")
};

var employees = new[]
{
    new Employee(1, "Ada", 1, 18000m, true),
    new Employee(2, "Bo", 1, 15000m, true),
    new Employee(3, "Chen", 2, 12000m, false),
    new Employee(4, "Ding", 2, 13500m, true),
    new Employee(5, "Evan", 3, 16000m, true)
};

下面所有示例都使用这两个数组,这样可以看清每一步如何改变结果。

筛选:Where

找出仍在职且月薪不低于 15000 的员工:

var selected = employees.Where(employee =>
    employee.Active && employee.Salary >= 15000m);

Where 接收一个返回布尔值的条件函数。对于 LINQ to Objects,结果在枚举时逐项判断;此时 selected 还不是已经复制好的新列表。

排序:OrderBy 与 ThenBy

从实际处理来看,先按部门升序,部门内再按薪资降序,同薪资按姓名升序:

var ordered = employees
    .Where(employee => employee.Active)
    .OrderBy(employee => employee.DepartmentId)
    .ThenByDescending(employee => employee.Salary)
    .ThenBy(employee => employee.Name);

第一个排序键用 OrderByOrderByDescending,后续键必须用 ThenBy 系列。如果连续调用两次 OrderBy,第二次会建立新的主排序,不是在第一个键内再排。

分组:GroupBy

按部门统计在职人数、平均薪资和最高薪资:

var summaries = employees
    .Where(employee => employee.Active)
    .GroupBy(employee => employee.DepartmentId)
    .Select(group => new
    {
        DepartmentId = group.Key,
        Count = group.Count(),
        AverageSalary = group.Average(employee => employee.Salary),
        MaxSalary = group.Max(employee => employee.Salary)
    })
    .OrderBy(summary => summary.DepartmentId)
    .ToList();

GroupBy 返回一组 IGrouping<TKey,TElement>group.Key 是分组键,group 本身可枚举该组内元素。用 Select 立即投影为统计结果,能让后续代码不必携带整组原始员工。

联接:Join

将员工的 DepartmentId 与部门的 Id 相等项联接,输出部门名:

var directory = employees
    .Where(employee => employee.Active)
    .Join(
        departments,
        employee => employee.DepartmentId,
        department => department.Id,
        (employee, department) => new
        {
            employee.Name,
            Department = department.Name,
            employee.Salary
        })
    .OrderBy(row => row.Department)
    .ThenByDescending(row => row.Salary)
    .ToList();

从实际处理来看,Join 的四个关键参数依次是:右侧数据源、左侧键、右侧键、结果投影。它是内联接,没有匹配部门的员工不会出现。

如果需要“所有部门,即使没有员工也要显示”,先用 GroupJoin 得到每个部门的员工组:

var departmentCounts = departments.GroupJoin(
    employees.Where(employee => employee.Active),
    department => department.Id,
    employee => employee.DepartmentId,
    (department, members) => new
    {
        Department = department.Name,
        ActiveCount = members.Count()
    });

Sales 等有员工的部门会显示真实数量,没有匹配项的部门会得到 0

把四种操作合成一份报表

要生成“各部门高薪在职员工数和平均薪资”,可先筛选,再按部门分组,然后与部门表联接,最后排序:

var salaryGroups = employees
    .Where(employee => employee.Active && employee.Salary >= 13000m)
    .GroupBy(employee => employee.DepartmentId)
    .Select(group => new
    {
        DepartmentId = group.Key,
        Count = group.Count(),
        Average = group.Average(employee => employee.Salary)
    });

var report = salaryGroups
    .Join(
        departments,
        group => group.DepartmentId,
        department => department.Id,
        (group, department) => new
        {
            Department = department.Name,
            group.Count,
            group.Average
        })
    .OrderByDescending(row => row.Average)
    .ThenBy(row => row.Department)
    .ToList();

ToList() 是这份报表的执行边界。它之前是查询定义,之后是当时结果的快照。

延迟执行和数据库 provider

从实际处理来看,很多返回 IEnumerable<T>IQueryable<T> 的 LINQ 操作只构建查询,到 foreachToListCount 等枚举/终结操作时才执行。因此:

  • 源集合在枚举前改变,结果也可能改变;
  • 多次枚举可能多次计算,对数据库则可能多次发 SQL;
  • 需要稳定快照时,在正确边界调用 ToList()
  • IQueryable<T>,自定义 C# 方法未必能翻译为 SQL,应查看 provider 的支持和实际生成查询。

结果不对时怎么查

  1. 排序次序不对:检查第二个键是否误用了 OrderBy 而非 ThenBy
  2. 联接后少数据:确认是否真的需要内联接,并检查两侧键的类型和值。
  3. 分组统计重复:检查是否在一对多 Join 放大行数后才做 Count,需要时先选取唯一标识。
  4. 每次输出都不一样:检查延迟执行期间源数据是否仍在变化,需要快照时及时物化。

总结

LINQ 的关键不是背下所有操作符,而是按结果形状组织步骤:Where 决定保留哪些项,OrderBy/ThenBy 决定顺序,GroupBy 将相同键变成组,Join 用共同键合并两个数据源。最后明确执行边界,并根据 LINQ to Objects 还是数据库 provider 验证实际行为。

相关文章

精彩推荐