跳到主要内容

2.6 集合与 LINQ

集合承载业务数据,LINQ 用声明式方式表达查询。它既用于内存集合,也用于 EF Core 数据库查询,但两者执行位置和性能特征不同。

学习目标

  • 能选择 List<T>Dictionary<TKey,TValue>HashSet<T> 等常用集合。
  • 能使用 LINQ 完成筛选、排序、投影、分组和聚合。
  • 能区分立即执行和延迟执行。
  • 能理解内存 LINQ 与 EF Core LINQ 的差异。

应用场景

  • 从任务列表中筛选未完成任务。
  • 根据标签、优先级或日期分组统计。
  • 把实体转换为返回给前端的 DTO。
  • 在服务层中组织业务计算。

核心概念

操作用途示例
Where筛选只要未完成任务
OrderBy排序按截止日期升序
Select投影实体转 DTO
GroupBy分组按状态统计数量
Any判断是否存在是否有逾期任务

案例:任务统计

var todos = new List<TodoItem>
{
new(1, "学习 C#", false, "学习"),
new(2, "完成项目切片", true, "项目"),
new(3, "复习 EF Core", false, "学习"),
};

var summary = todos
.GroupBy(todo => todo.Category)
.Select(group => new TodoCategorySummary(
group.Key,
Total: group.Count(),
Completed: group.Count(todo => todo.IsCompleted)))
.OrderByDescending(item => item.Total)
.ToList();

public sealed record TodoItem(int Id, string Title, bool IsCompleted, string Category);
public sealed record TodoCategorySummary(string Category, int Total, int Completed);

示例:EF Core 查询中的 LINQ

var todos = await dbContext.Todos
.AsNoTracking()
.Where(todo => !todo.IsCompleted)
.OrderBy(todo => todo.DueDate)
.Select(todo => new TodoListItemDto(todo.Id, todo.Title, todo.DueDate))
.Take(20)
.ToListAsync(cancellationToken);

这段 LINQ 会被 EF Core 翻译成 SQL。不要在 ToListAsync 之前调用无法翻译的 C# 方法,否则可能导致查询失败或提前加载大量数据。

重点难点

  • IEnumerable<T> 通常表示内存枚举,IQueryable<T> 通常表示可被查询提供程序翻译。
  • ToList()Count()First() 会触发立即执行。
  • 多次枚举可能重复执行查询或重复计算。
  • 数据库分页必须在数据库侧完成,不要先取全量再分页。

常见误区

误区推荐做法
写出长链 LINQ 但没人读得懂适当拆中间变量并命名业务含义
在 EF 查询中调用本地方法使用可翻译表达式或先明确切到内存
以为 Where 立刻执行理解延迟执行,知道什么时候触发

练习

  • 按任务状态统计数量。
  • 找出每个分类下最近创建的 3 个任务。
  • 把实体列表投影为只包含 IdTitleIsCompleted 的 DTO。