C# Linq语法 作者:马育民 • 2025-03-22 22:35 • 阅读:10052 # 介绍 Linq(Language Integrated Query),语言集成查询,是一种使用 **类似SQL语句** 操作多种数据源的功能。 从.net framework3.5中开始引入,能够提升程序数据处理能力和开发效率,具有集成性、统一性、可扩展性、抽象性、说明式编程、可组成型、可转换性等优势。 ### 支持的数据源 包括但不限于: - **内存中的集合**(如 `List`,`Array`,`SortedSet`,`Stack`,`Queue` 等) - 数据库(如SQL Server) - XML文档 - Web服务 ### 类比 Java 的 stream 类似 Java 的 stream,但功能更强大 ### 实现方式 有两种: - 方法查询。类似 Java 的 stream,通过集合的 **扩展方法** 和 **lambda表达式** 实现 - 查询表达式语法,类似 SQL 语句(某些功能不支持,需要方法查询) **原理:** 标准查询运算符是一组 **扩展方法**,它们提供了一种 **函数式** 的方式来构建查询。这些方法定义在 `System.Linq.Enumerable` 类中,适用于所有实现了 `IEnumerable` 接口的集合。 ### 前提条件 使用 LINQ 方法语法需满足: - 引用 `System.Linq` 命名空间(默认在 C# 项目中已包含)。 - 数据源需实现 `IEnumerable` 或 `IQueryable` 接口(如 `List`、`Array`、数据库查询结果等)。 # 方法查询 方法语法(也称为流利语法)主要利用 `System.Linq.Enumerable` 类中定义的扩展方法和 `Lambda表达式` 方式进行查询,类似于如何调用任何类的扩展方法 ### 例子 以下是一个示例LINQ方法语法的查询,返回数组中的偶数: ``` var nums = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 }; var result = nums.Where(p => p % 2 == 0).ToArray(); ``` 从上面的示例代码中可以看出:方法语法包括扩展方法和Lambda表达式。 扩展方法 `Where()`在Enumerable类中定义。 ### 准备数据 ```csharp using System; using System.Collections.Generic; using System.Linq; // 定义实体类 public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Major { get; set; } public double Score { get; set; } } // 数据源:学生列表 List students = new List { new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机", Score = 85.5 }, new Student { Id = 2, Name = "李四", Age = 22, Major = "数学", Score = 92.0 }, new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机", Score = 78.0 }, new Student { Id = 4, Name = "赵六", Age = 21, Major = "数学", Score = 88.5 }, new Student { Id = 5, Name = "钱七", Age = 20, Major = "计算机", Score = 90.0 } }; ``` ### 过滤 Where **作用**:筛选出符合条件的元素,返回 `IEnumerable`。 **参数**:Lambda 表达式(`Func`),定义筛选条件。 ```csharp // 示例:查询“计算机”专业的学生 var csStudents = students.Where(s => s.Major == "计算机"); // 遍历结果 foreach (var s in csStudents) { Console.WriteLine($"{s.Id}: {s.Name} ({s.Major})"); } // 输出: // 1: 张三 (计算机) // 3: 王五 (计算机) // 5: 钱七 (计算机) ``` ### 投影 Select **作用**:将元素转换为新的形式(如提取部分字段、转换类型),返回 `IEnumerable`。 **参数**:Lambda 表达式(`Func`),定义转换规则。 ```csharp // 示例:提取学生的“姓名”和“分数”,转换为匿名类型 var nameAndScores = students.Select(s => new { s.Name, s.Score }); foreach (var item in nameAndScores) { Console.WriteLine($"{item.Name}: {item.Score}"); } // 输出: // 张三: 85.5 // 李四: 92.0 // ...(省略其他) ``` ### 排序(`OrderBy` / `OrderByDescending`) **作用**:对元素按指定字段排序,`OrderBy` 升序,`OrderByDescending` 降序。 **参数**:Lambda 表达式(`Func`),指定排序字段。 ```csharp // 示例:按分数降序排列所有学生 var sortedByScore = students.OrderByDescending(s => s.Score); foreach (var s in sortedByScore) { Console.WriteLine($"{s.Name}: {s.Score}"); } // 输出: // 李四: 92.0 // 钱七: 90.0 // 张三: 85.5 // 赵六: 88.5(注意:88.5 < 90.0,所以在钱七之后) // 王五: 78.0 ``` ### 限制结果(`Take` / `Skip`) - `Take(n)`:获取前 `n` 个元素。 - `Skip(n)`:跳过前 `n` 个元素,获取剩余元素。 ```csharp // 示例:取分数最高的前2名学生 var top2 = students.OrderByDescending(s => s.Score).Take(2); foreach (var s in top2) { Console.WriteLine($"{s.Name}: {s.Score}"); } // 输出: // 李四: 92.0 // 钱七: 90.0 ``` ### 聚合(`Count` / `Sum` / `Average` / `Max` / `Min`) **作用**:对集合进行统计计算(立即执行,非延迟执行)。 ```csharp // 示例:统计计算机专业学生的数量、平均年龄、最高分数 int csCount = students.Where(s => s.Major == "计算机").Count(); double avgAge = students.Where(s => s.Major == "计算机").Average(s => s.Age); double maxScore = students.Where(s => s.Major == "计算机").Max(s => s.Score); Console.WriteLine($"计算机专业人数:{csCount},平均年龄:{avgAge},最高分数:{maxScore}"); // 输出:计算机专业人数:3,平均年龄:19.666...,最高分数:90.0 ``` ### 分组(`GroupBy`) **作用**:按指定字段将元素分组,返回 `IEnumerable>`。 ```csharp // 示例:按专业分组,获取每个专业的学生 var groupByMajor = students.GroupBy(s => s.Major); foreach (var group in groupByMajor) { Console.WriteLine($"专业:{group.Key},人数:{group.Count()}"); foreach (var s in group) { Console.WriteLine($" {s.Name}"); } } // 输出: // 专业:计算机,人数:3 // 张三 // 王五 // 钱七 // 专业:数学,人数:2 // 李四 // 赵六 ``` ### 连接(`Join`) **作用**:类似 SQL 的 `JOIN`,根据两个集合的共同字段关联数据。 假设有第二个数据源(课程): ```csharp List courses = new List { new Course { CourseId = 101, CourseName = "C#编程", Major = "计算机" }, new Course { CourseId = 102, CourseName = "高等数学", Major = "数学" } }; public class Course { public int CourseId { get; set; } public string CourseName { get; set; } public string Major { get; set; } } ``` ```csharp // 示例:关联学生和课程(按专业),获取“学生姓名-专业-课程名” var studentCourses = students.Join( courses, s => s.Major, // 学生集合的关联字段 c => c.Major, // 课程集合的关联字段 (s, c) => new { // 关联后的结果 StudentName = s.Name, Major = s.Major, CourseName = c.CourseName } ); foreach (var item in studentCourses) { Console.WriteLine($"{item.StudentName} - {item.Major} - {item.CourseName}"); } // 输出: // 张三 - 计算机 - C#编程 // 王五 - 计算机 - C#编程 // 钱七 - 计算机 - C#编程 // 李四 - 数学 - 高等数学 // 赵六 - 数学 - 高等数学 ``` ### 方法链(Method Chaining) LINQ 方法语法支持**链式调用**,将多个方法组合成一个查询,可读性和灵活性更高。 ```csharp // 示例:查询计算机专业中年龄>19、分数>80的学生,按分数升序,取前2名,只显示姓名和分数 var result = students .Where(s => s.Major == "计算机" && s.Age > 19 && s.Score > 80) // 过滤 .OrderBy(s => s.Score) // 排序 .Take(2) // 限制数量 .Select(s => new { s.Name, s.Score }); // 投影 foreach (var item in result) { Console.WriteLine($"{item.Name}: {item.Score}"); } // 输出: // 张三: 85.5 // 钱七: 90.0 ``` ### 延迟执行 vs 立即执行 - **延迟执行**:`Where`、`Select`、`OrderBy`、`GroupBy` 等方法不会立即执行,仅定义查询规则,直到枚举结果时才执行(如 `foreach`、`ToList()`)。 优点:可组合多个查询步骤,最终一次性执行,提高效率。 - **立即执行**:`Count()`、`Sum()`、`ToList()`、`ToArray()` 等方法会立即执行查询并返回结果。 ```csharp // 延迟执行:此时未执行查询 var query = students.Where(s => s.Age > 20); // 立即执行:调用 ToList() 时执行查询 var list = query.ToList(); ``` ### 总结 LINQ 方法语法通过扩展方法和 Lambda 表达式,提供了简洁、灵活的查询能力,核心优势: 1. **统一查询语法**:对各种数据源(集合、数据库、XML 等)使用相同的查询方式。 2. **类型安全**:编译时检查查询语法和类型,减少运行时错误。 3. **可组合性**:通过方法链组合多个操作,实现复杂查询。 掌握常用方法(`Where`、`Select`、`OrderBy`、`GroupBy` 等)及其组合使用,是高效开发 .NET 应用的基础。 # 查询表达式语法 类似 SQL 语句(某些功能不支持,需要方法查询) 常见的标准查询运算符包括: - Where:过滤集合中的元素。 - Select:投影每个元素。 - OrderBy 和 OrderByDescending:按升序或降序排序。 - GroupBy:根据键值对元素进行分组。 - Join:将两个集合基于键值进行关联。 - Any 和 All:检查集合是否满足某些条件。 - Count 和 Sum:计算集合的大小或求和。 ## 整体语法 ``` from source in collection:指定查询的数据源。 where condition:指定筛选条件。 orderby key [descending]:指定排序条件。 select result:定义查询结果,一般以 select 结尾 ``` ## 基本查询 #### 语法 ``` from item in collection:指定查询的数据源 select item:定义查询结果 ``` **理解:** `from item in collection` 类似 `foreach( var item in collection )` #### 例子 ``` var nums = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 }; //num为数据中的每个元素(相当于foreach中迭代变量) var queryInfo = from num in nums select num; foreach (var item in queryInfo) { System.Diagnostics.Debug.WriteLine(item); } ``` 执行结果: ``` 97 98 74 86 90 ``` ## where条件查询 where子句用来指定筛选的条件 #### 语法 ``` from item in collection:指定查询的数据源。 where condition:指定筛选条件。 select item:定义查询结果 ``` #### 例子 ``` var nums = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 }; //num为数据中的每个元素(相当于foreach中迭代变量) var queryInfo = from num in nums where num >60 select num; foreach (var item in queryInfo) { System.Diagnostics.Debug.WriteLine(item); } ``` 执行结果: ``` 97 98 74 86 90 ``` ## select查询列 select子句,基于查询结果返回需要的 字段,并能够对返回值指定类型。对任意想要获取到返回结果的linq语句,必须以 `select` 或 `group` 结束。 准备工作: ``` class Person { public string Name { set; get; } public int Age { set; get; } public string Gender { set; get; } public override string ToString() => Name; } ``` 初始化 `List`: ``` static List init() { List personList = new List { new Person { Name = "李雷", Age = 18, Gender = "Male" }, new Person { Name = "张三", Age = 19, Gender = "Male", }, new Person { Name = "韩梅梅", Age = 17,Gender = "Female", } }; return personList; } ``` #### 查询一列 ``` List list = init(); // 当 item 是对象时,可以只查询某个属性 var queryInfo = from item in list select item.Name; foreach (var item in queryInfo) { Console.WriteLine(item); } ``` 执行结果: ``` 李雷 张三 韩梅梅 ``` #### 查询整个对象 ``` List list = init(); // 当 item 是对象时,也可以查询 item var queryInfo = from item in list select item; foreach (var item in queryInfo) { Console.WriteLine(item.Name+","+item.Age); } ``` 执行结果: ``` 李雷,18 张三,19 韩梅梅,17 ``` #### 查询多列 ``` List list = init(); // Select 后面跟匿名类,封装查询的字段 var queryInfo = from item in list select new { username = item.Name, uesrAge = item.Age }; foreach (var item in queryInfo) { Console.WriteLine(item.username + ","+item.uesrAge); } ``` 执行结果: ``` 李雷,18 张三,19 韩梅梅,17 ``` ## order by linq中语句默认展示为升序,降序使用 `descending` #### 语法 ``` from item in collection:指定查询的数据源。 where condition:指定筛选条件。 orderby key [descending]:指定排序条件。 select 字段:定义查询结果,一般以 select 结尾 ``` #### 例子-升序 ``` var scores = new int[] { 56, 97, 98, 57, 74, 86, 31, 90 }; var query = from item in scores orderby item select item;//升序 foreach (var item in query) { Console.WriteLine(item); } ``` #### 例子-降序 ``` //var orderQuery = from item in scores orderby item descending select item;//降序 ``` ## group by `group by` 子句用来对查询结果进行分组 - 如果结果分为两组,且 **未指定key** 的情况下,`key` 取值默认是 `true` 和 `false`。 - 如果分为多组,获取数据结果时需要手动遍历 `key` 获取对应的 `value` #### 语法 ``` from item in collection:指定查询的数据源。 group item by item.属性 ``` #### 例子 ``` List list = init(); // Select 后面跟匿名类,封装查询的字段 var queryInfo = from item in list group item by item.Gender; foreach (var item in queryInfo) { Console.WriteLine(item.Key); } ``` ## group by 聚合查询 #### 例子 ``` List list = init(); // Select 后面跟匿名类,封装查询的字段 var queryInfo = from item in list group item by item.Gender into g select new { Key = g.Key, count = g.Count(), maxAge = g.Max( x=> x.Age), minAge = g.Min(x => x.Age), sumAge = g.Sum(x => x.Age), avgAge = g.Average(s => s.Age) }; foreach (var item in queryInfo) { Console.WriteLine($"{item.Key}---总行数:{item.count},最大年龄:{item.maxAge},最小年龄:{item.minAge},年龄和:{item.sumAge},平均年龄:{item.avgAge}"); } ``` 执行结果: ``` Male---总行数:2,最大年龄:19,最小年龄:18,年龄和:37,平均年龄:18.5 Female---总行数:1,最大年龄:17,最小年龄:17,年龄和:17,平均年龄:17 ``` 参考: https://blog.csdn.net/m0_56259289/article/details/144134122 https://blog.csdn.net/qq_34550459/article/details/113540805 https://zhuanlan.zhihu.com/p/146747701 原文出处:http://malaoshi.top/show_1GWo93Q1dMS.html