Skip to content

LINQ 数据查询技术实践指南 ​

LINQ (Language Integrated Query) 是 .NET 中用于查询数据的强大工具。它将查询功能直接集成到 C# 语言中,使得查询代码更加简洁、安全且易于维护。只要是实现了 IEnumerable<T> 或 IQueryable<T> 的对象,都可以使用 LINQ 进行查询。

1. 基础语法与核心关键字 ​

LINQ 提供两种语法风格:查询表达式语法 (Query Syntax) 和 方法语法 (Method Syntax)。

1.1 核心关键字 ​

在 LINQ 中,核心关键字的作用与 SQL 类似,但顺序有所不同:

  • from: 指定数据源和范围变量。
  • where: 筛选条件。
  • select: 指定返回的结果格式。
  • group...by...into: 分组并将结果推入新范围。
  • join: 关联查询。
  • orderby: 排序。
  • let: 引入临时变量。

1.2 查询语法示例 ​

csharp
var query = from p in products
            where p.Price > 10
            orderby p.Name
            select new { p.Name, p.Price };

2. 关联查询 (Join) 实战 ​

2.1 多表内连接 (Inner Join) ​

当需要关联三个及以上的表时,查询表达式语法比 Lambda 链式调用更直观:

csharp
var query = from user in Users
            join userRole in UserRoles on user.Id equals userRole.UserId
            join role in Roles on userRole.RoleId equals role.Id
            join pr in PermissionRoles on role.Id equals pr.Role_Id
            select new {
                user.FullName,
                RoleName = role.Name,
                Permission = pr.Permission.Name
            };

2.2 链式左外连接 (Left Outer Join) ​

实现左连接的关键是 into 配合 DefaultIfEmpty()。如果需要连续左连多张表,可以按如下方式链式操作:

csharp
var query = from member in Members
            join order in Orders on member.Id equals order.MemberId into gj1
            from subOrder in gj1.DefaultIfEmpty()
            join detail in OrderDetails on subOrder.Id equals detail.OrderId into gj2
            from subDetail in gj2.DefaultIfEmpty()
            select new {
                member.Name,
                OrderInfo = subOrder,
                DetailInfo = subDetail
            };

2.4 集合扁平化 (SelectMany) ​

当一个对象包含集合属性,且需要将所有子集合展开为一个平坦序列时使用:

csharp
// 查询所有订单的明细项,并扁平化显示
var allDetails = orders.SelectMany(o => o.OrderDetails);

// 带过滤的扁平化
var filteredDetails = from o in orders
                     from d in o.OrderDetails
                     where d.Quantity > 5
                     select new { o.OrderNo, d.ProductName };

3. 分组查询 (Group By) 高级场景 ​

分组查询返回的是一个 IGrouping<K, T> 集合,其中包含 Key 和对应的元素列表。

3.1 常见聚合操作 ​

  1. 基础统计:group p by p.CategoryID into g 后直接 select。
  2. 最大/最小值:MaxPrice = g.Max(p => p.UnitPrice)。
  3. 平均值与求和:AveragePrice = g.Average(p => p.UnitPrice)。
  4. Where 过滤组:在 into g 之后使用 where g.Count() > 10(相当于 SQL 的 HAVING)。

3.2 多列分组 (Multiple Columns) ​

通过匿名类实现多字段分组:

csharp
var groups = from p in products
             group p by new { p.CategoryID, p.SupplierID } into g
             select new {
                 g.Key.CategoryID,
                 g.Key.SupplierID,
                 Count = g.Count()
             };

3.3 表达式分组 (Expression Grouping) ​

按布尔表达式的结果(如“是否贵价”)进行分类:

csharp
var groups = from p in products
             group p by new { IsExpensive = p.UnitPrice > 100 } into g
             select g;

4. 实用技巧与常见问题 ​

4.1 Let 关键字与临时变量 ​

let 允许在查询中存储子表达式的结果,避免重复计算:

csharp
var query = from p in products
            let totalStockValue = p.UnitPrice * p.UnitsInStock
            where totalStockValue > 500
            select new { p.Name, Value = totalStockValue };

4.2 Into 关键字与查询延续 ​

into 可以将查询结果暂时存放,用于后续的进一步过滤或分组:

csharp
var query = from p in products
            group p by p.CategoryID into g
            where g.Count() > 5
            select new { CategoryID = g.Key, ProductCount = g.Count() };

4.3 查询数组中出现次数最多的数字 ​

csharp
int[] values = { 1, 2, 5, 2, 3, 5, 5, 3, 4, 6, 3, 3 };
var mostFrequent = values.GroupBy(v => v)
                         .Select(g => new { g.Key, Count = g.Count() })
                         .OrderByDescending(x => x.Count)
                         .First().Key;

4.2 Any() vs Count() ​

  • Any():只要找到第一个匹配项就返回,翻译为 SQL 的 EXISTS,性能极高。
  • Count():会遍历整个序列,如果是数据库查询,会执行 COUNT(*),开销较大。
  • 原则:仅判断是否存在时,永远优先使用 Any()。

5. 性能优化与注意事项 ​

  • 延迟执行 (Deferred Execution):LINQ 查询在 foreach 迭代或调用 ToList()、Count() 等立即执行函数前,不会真正去数据库取数。
  • IQueryable 与 IEnumerable 的边界:
    • 在数据库操作中,务必在 IQueryable 阶段完成所有的 Where 和 Join 过滤。
    • 一旦调用 AsEnumerable() 或 ToList(),后续的操作将在内存中进行,可能导致全表扫描。
  • 避免 N+1 查询:使用导航属性时,配合 .Include(x => x.Relation) 进行预加载。

6. 为什么 LINQ 优于原生 SQL? ​

在 .NET 开发中,虽然 SQL 很强大,但 LINQ 提供了以下不可替代的优势:

  1. 强类型检查:在编译期即可发现字段名错误或类型不匹配,而 SQL 错误只能在运行时发现。
  2. 智能提示 (IntelliSense):编码时可实时查看字段和方法,极大提高效率。
  3. 可组合性:可以通过传递 IQueryable 对象,在不同的业务层动态添加 Where 条件,实现极其灵活的查询构建。
  4. 安全性:天然防御 SQL 注入攻击。
  5. 多数据源统一:无论是 SQL Server、MySQL、XML 还是内存集合,语法高度一致。

7. 参考资料 ​

方舟 · Ark