首页 > 代码库 > [C#] 开始使用 LINQ
[C#] 开始使用 LINQ
开始使用 LINQ
序
语言集成查询 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版中引入的一项创新功能。
传统上,针对数据的查询都是以简单的字符串表示,而没有编译时类型检查或 IntelliSense 支持。此外,您还必须针对以下各种数据源学习一种不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等等。 通过LINQ, 您可以使用语言关键字和熟悉的运算符针对强类型化对象集合编写查询。
目录
- 介绍 LINQ 查询
- LINQ 基本查询操作
- 使用 LINQ 进行数据转换
- LINQ 查询操作的类型关系
- LINQ 中的查询语法和方法语法
一、介绍 LINQ 查询
查询是一种从数据源检索数据的表达式。随着时间的推移,人们已经为各种数据源开发了不同的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。因此,开发人员不得不针对他们必须支持的每种数据源或数据格式而学习新的查询语言。LINQ 通过提供一种跨数据源和数据格式使用数据的一致模型,简化了这一情况。在 LINQ 查询中,始终会用到对象。可以使用相同的编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及对其有 LINQ 提供程序可用的任何其他格式的数据。
1.1 查询操作的三个部分
操作三部曲:①取数据源 ②创建查询 ③执行查询
1 internal class Program 2 { 3 private static void Main(string[] args) 4 { 5 //1.获取数据源 6 var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; 7 8 //2.创建查询 9 var numQuery =10 from num in nums11 where (num % 2) == 012 select num;13 14 //3.执行查询15 foreach (var num in numQuery)16 {17 Console.WriteLine("{0}", num);18 }19 }20 }
下图显示了完整的查询操作。在 LINQ 中,查询的执行与查询本身截然不同;换句话说,查询本身指的是只创建查询变量,不检索任何数据。
1.2 数据源
在上一个示例中,由于数据源是数组,因此它隐式支持泛型 IEnumerable<T> 接口。支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的类型称为可查询类型。
//从 XML 中创建数据源 //using System.Xml.Linq; var contacts = XElement.Load(@"c:\xxx.xml");
在 LINQ to SQL 中,首先需要创建对象关系映射。 针对这些对象编写查询,然后由 LINQ to SQL 在运行时处理与数据库的通信。
1 var db = new Northwnd(@"c:\northwnd.mdf");2 3 //查询在伦敦的客户4 var custQuery =5 from cust in db.Customers6 where cust.City == "London"7 select cust;
1.3 查询
查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。
1.4 查询执行
1.延迟执行
如前所述,查询变量本身只是存储查询命令。 实际的查询执行会延迟到在 foreach 语句中循环访问查询变量时发生。 此概念称为“延迟执行”。
2.强制立即执行
对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。Count、Max、Average 和 First 就属于此类查询。由于查询本身必须使用 foreach 以便返回结果,因此这些查询在执行时不使用显式 foreach 语句。另外还要注意,这些类型的查询返回单个值,而不是 IEnumerable 集合。
1 var numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };2 3 var evenNumQuery =4 from num in numbers5 where (num % 2) == 06 select num;7 8 var evenNumCount = evenNumQuery.Count();
若要强制立即执行任意查询并缓存其结果,可以调用 ToList<TSource> 或 ToArray<TSource> 方法。
1 var numQuery2 =2 (from num in numbers3 where (num % 2) == 04 select num).ToList();5 6 var numQuery3 =7 (from num in numbers8 where (num % 2) == 09 select num).ToArray();
此外,还可以通过在紧跟查询表达式之后的位置放置一个 foreach 循环来强制执行查询。但是,通过调用 ToList 或 ToArray,也可以将所有数据缓存在单个集合对象中。
二、基本 LINQ 查询操作
2.1 获取数据源:from
在 LINQ 查询中,第一步是指定数据源。像在大多数编程语言中一样,必须先声明变量,才能使用它。在 LINQ 查询中,最先使用 from 子句的目的是引入数据源和范围变量。
1 //queryAllCustomers 是 IEnumerable<Cutsomer> 类型2 //数据源 (customers) 和范围变量 (cust)3 var queryAllCustomers = from cust in customers4 select cust;
范围变量类似于 foreach 循环中的迭代变量,但在查询表达式中,实际上不发生迭代。执行查询时,范围变量将用作对 customers 中的每个后续元素的引用。因为编译器可以推断 cust 的类型,所以您不必显式指定此类型。
2.2 筛选:where
也许最常用的查询操作是应用布尔表达式形式的筛选器。此筛选器使查询只返回那些表达式结果为 true 的元素。使用 where 子句生成结果。实际上,筛选器指定从源序列中排除哪些元素。
1 var queryLondonCustomers = from cust in customers2 where cust.City = "London"3 select cust;
您可以使用熟悉的 C# 逻辑 AND(&&)和 OR(||) 运算符来根据需要在 where 子句中应用任意数量的筛选表达式。
where cust.City = "London" && cust.Name = "Devon"
where cust.City = "London" || cust.Name = "Paris"
2.3 排序:orderby
通常可以很方便地将返回的数据进行排序。orderby 子句将使返回的序列中的元素按照被排序的类型的默认比较器进行排序。
1 var queryLondonCustomers = from cust in customers2 where cust.City = "London"3 orderby cust.Name descending 4 select cust;
因为 Name 是一个字符串,所以默认比较器执行从 A 到 Z 的字母排序。若要按相反顺序(从 Z 到 A)对结果进行排序,请使用 orderby…descending 子句。
2.4 分组:group
使用 group 子句,您可以按指定的键分组结果。
1 var queryLondonCustomers = from cust in customers 2 group cust by cust.City; 3 4 foreach (var queryLondonCustomer in queryLondonCustomers) 5 { 6 Console.WriteLine(queryLondonCustomer.Key); 7 foreach (var cust in queryLondonCustomer) 8 { 9 Console.WriteLine(cust.Name);10 }11 }
在本例中,cust.City 是键。
在使用 group 子句结束查询时,结果采用列表的列表形式。列表中的每个元素是一个具有 Key 成员及根据该键分组的元素列表的对象。在循环访问生成组序列的查询时,您必须使用嵌套的 foreach 循环。外部循环用于循环访问每个组,内部循环用于循环访问每个组的成员。
如果您必须引用组操作的结果,可以使用 into 关键字来创建可进一步查询的标识符。
1 //custQuery 是 IEnumable<IGrouping<string, Customer>> 类型2 var custQuery = from cust in customers3 group cust by cust.City4 into custGroup5 where custGroup.Count() > 26 orderby custGroup.Key7 select custGroup;
2.5 联接:join
联接运算创建数据源中没有显式建模的序列之间的关联。例如,您可以执行联接来查找位于同一地点的所有客户和经销商。在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。
1 var innerJoinQuery = from cust in customers2 join dist in distributors on cust.City equals dist.City3 select new {CustomerName = cust.Name, DistributorName = dist.Name};
在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。
在 LINQ 中,您不必像在 SQL 中那样频繁使用 join,因为 LINQ 中的外键在对象模型中表示为包含项集合的属性。
from order in Customer.Orders...
2.6 选择(投影):select
select 子句生成查询结果并指定每个返回的元素的“形状”或类型。
例如,您可以指定结果包含的是整个 Customer 对象、仅一个成员、成员的子集,还是某个基于计算或新对象创建的完全不同的结果类型。当 select 子句生成除源元素副本以外的内容时,该操作称为“投影”。
三、使用 LINQ 进行数据转换
语言集成查询 (LINQ) 不仅可用于检索数据,而且还是一个功能强大的数据转换工具。通过使用 LINQ 查询,您可以将源序列用作输入,并采用多种方式修改它以创建新的输出序列。您可以通过排序和分组来修改该序列,而不必修改元素本身。但是,LINQ 查询的最强大的功能是能够创建新类型。这一功能在 select 子句中实现。 例如,可以执行下列任务:
3.1 将多个输入联接到一个输出序列
1 class Student 2 { 3 public string Name { get; set; } 4 5 public int Age { get; set; } 6 7 public string City { get; set; } 8 9 public List<int> Scores { get; set; }10 }11 12 class Teacher13 {14 public int Id { get; set; }15 16 public string Name { get; set; }17 18 public int Age { get; set; }19 20 public string City { get; set; }21 22 }
1 internal class Program 2 { 3 private static void Main(string[] args) 4 { 5 //创建第一个数据源 6 var students = new List<Student>() 7 { 8 new Student() 9 {10 Age = 23,11 City = "广州",12 Name = "小C",13 Scores = new List<int>(){85,88,83,97}14 },15 new Student()16 {17 Age = 18,18 City = "广西",19 Name = "小明",20 Scores = new List<int>(){86,78,85,90}21 },22 new Student()23 {24 Age = 33,25 City = "梦里",26 Name = "小叁",27 Scores = new List<int>(){86,68,73,97}28 }29 };30 31 //创建第二个数据源32 var teachers = new List<Teacher>()33 {34 new Teacher()35 {36 Age = 35,37 City = "梦里",38 Name = "啵哆"39 },40 new Teacher()41 {42 Age = 28,43 City = "云南",44 Name = "小红"45 },46 new Teacher()47 {48 Age = 38,49 City = "河南",50 Name = "丽丽"51 }52 };53 54 //创建查询55 var peopleInDreams = (from student in students56 where student.City == "梦里"57 select student.Name)58 .Concat(from teacher in teachers59 where teacher.City == "梦里"60 select teacher.Name);61 62 //执行查询63 foreach (var person in peopleInDreams)64 {65 Console.WriteLine(person);66 }67 68 Console.Read();69 }70 }
3.2 选择各个源元素的子集
1. 若要只选择源元素的一个成员,请使用点运算。
1 var query = from cust in Customers2 select cust.City;
2. 若要创建包含源元素的多个属性的元素,可以使用具有命名对象或匿名类型的对象初始值设定项。
1 var query = from cust in Customer2 select new {Name = cust.Name, City = cust.City};
3.3 将内存中的对象转换为 XML
1 //创建数据源 2 var students = new List<Student>() 3 { 4 new Student() 5 { 6 Age = 18, 7 Name = "小A", 8 Scores = new List<int>() {88,85,74,66 } 9 },10 new Student()11 {12 Age = 35,13 Name = "小B",14 Scores = new List<int>() {88,85,74,66 }15 },16 new Student()17 {18 Age = 28,19 Name = "小啥",20 Scores = new List<int>() {88,85,74,66 }21 }22 };23 24 //创建查询25 var studentsToXml = new XElement("Root",26 from student in students27 let x = $"{student.Scores[0]},{student.Scores[1]},{student.Scores[2]},{student.Scores[3]}"28 select new XElement("student",29 new XElement("Name", student.Name),30 new XElement("Age", student.Age),31 new XElement("Scores", x))32 );33 34 //执行查询35 Console.WriteLine(studentsToXml);
3.4 对源元素执行操作
输出序列可能不包含源序列的任何元素或元素属性。输出可能是通过将源元素用作输入参数计算出的值的序列。
1 //数据源 2 double[] radii = {1, 2, 3}; 3 4 //创建查询 5 var query = from radius in radii 6 select $"{radius * radius * 3.14}"; 7 8 //执行查询 9 foreach (var i in query)10 {11 Console.WriteLine(i);12 }
四、LINQ 查询操作的类型关系
LINQ 查询操作在数据源、查询本身及查询执行中是强类型的。查询中变量的类型必须与数据源中元素的类型和 foreach 语句中迭代变量的类型兼容。此强类型保证在编译时捕获类型错误,以便可以在用户遇到这些错误之前更正它们。
4.1 不转换源数据的查询
下图演示不对数据执行转换的 LINQ to Objects 查询操作。源包含一个字符串序列,查询输出也是一个字符串序列。
(1)数据源的类型参数决定范围变量的类型。
(2)选择的对象的类型决定查询变量的类型。此处的 name 为一个字符串。因此,查询变量是一个 IEnumerable<字符串>。
(3)在 foreach 语句中循环访问查询变量。因为查询变量是一个字符串序列,所以迭代变量也是一个字符串。
4.2 转换源数据的查询
下图演示对数据执行简单转换的 LINQ to SQL 查询操作。查询将一个 Customer 对象序列用作输入,并只选择结果中的 Name 属性。因为 Name 是一个字符串,所以查询生成一个字符串序列作为输出。
(1)数据源的类型参数决定范围变量的类型。
(2)select 语句返回 Name 属性,而非完整的 Customer 对象。因为 Name 是一个字符串,所以 custNameQuery 的类型参数是 string,而非Customer。
(3)因为 custNameQuery 是一个字符串序列,所以 foreach 循环的迭代变量也必须是 string。
下图演示另一种转换。select 语句返回只捕获原始 Customer 对象的两个成员的匿名类型。
(1)数据源的类型参数始终为查询中的范围变量的类型。
(2)因为 select 语句生成匿名类型,所以必须使用 var 隐式类型化查询变量。
(3)因为查询变量的类型是隐式的,所以 foreach 循环中的迭代变量也必须是隐式的。
4.3 让编译器推断类型信息
您也可以选择让编译器为您执行全部工作。关键字 var 可用于查询操作中的任何局部变量。但是,编译器为查询操作中的各个变量提供强类型。
五、LINQ 中的查询语法和方法语法
在表示语言集成查询 (LINQ) 使用 LINQ 性查询语法,文档中的多数查询编写。但是,编译代码时,必须将查询语法转换为方法,这就需要 .NET 公共语言运行时 (CLR)。这些方法调用标准查询运算符的名称类似 Where、Select、GroupBy、Join、Max和 Average。可以调用这些方法直接使用方法语法而不是查询语法。
查询语法和方法语法语义相同,但是,许多人员发现查询语法更简单、更易于阅读。某些查询必须表示为方法调用。例如,必须使用方法调用表示检索元素的数量与指定的条件的查询。还必须使用方法需要检索元素的最大值在源序列的查询。System.Linq 命名空间中的标准查询运算符的参考文档通常使用方法语法。
[C#] 开始使用 LINQ