[C#] 走进 LINQ 的世界

走进 LINQ 的世界

  在此以前曾发表过三篇关于 LINQ 的随笔:html

    进阶:《LINQ 标准查询操做概述(强烈推荐)数据库

    技巧:《Linq To Objects - 如何操做字符串》 和 《Linq To Objects - 如何操做文件目录编程

  如今,本身打算再整理一篇关于 LINQ 入门的随笔,也是图文并茂的哦。数组

 

目录

 

LINQ 简介

  语言集成查询 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版中引入的一项创新功能。缓存

  传统上,针对数据的查询都是以简单的字符串表示,而没有编译时类型检查或 IntelliSense 支持。此外,您还必须针对如下各类数据源学习一种不一样的查询语言:SQL 数据库、XML 文档、各类 Web 服务等等。 经过LINQ, 您可使用语言关键字和熟悉的运算符针对强类型化对象集合编写查询。编程语言

  

  在 Visual Studio 中,能够为如下数据源编写 LINQ 查询:SQL Server 数据库、XML 文档、ADO.NET 数据集,以及支持  IEnumerable 或泛型  IEnumerable<T> 接口的任意对象集合。
  使用要求:项目 ≥ .NET Framework 3.5 。
 

1、介绍 LINQ 查询

  查询是一种从数据源检索数据的表达式。随着时间的推移,人们已经为各类数据源开发了不一样的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。所以,开发人员不得不针对他们必须支持的每种数据源或数据格式而学习新的查询语言。LINQ 经过提供一种跨数据源和数据格式使用数据的一致模型,简化了这一状况。在 LINQ 查询中,始终会用到对象。可使用相同的编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及对其有 LINQ 提供程序可用的任何其余格式的数据。  ide

 

  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 nums
11                 where (num % 2) == 0
12                 select num;
13 
14             //3.执行查询
15             foreach (var num in numQuery)
16             {
17                 Console.WriteLine("{0}", num);
18             }
19         }
20 }
View Code

 

   下图显示了完整的查询操做。在 LINQ 中,查询的执行与查询自己大相径庭;换句话说,查询自己指的是只建立查询变量,不检索任何数据。工具

  

  1.2 数据源

  在上一个示例中,因为数据源是数组,所以它隐式支持泛型 IEnumerable<T> 接口。支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的类型称为可查询类型。  post

  可查询类型不须要进行修改或特殊处理就能够用做 LINQ 数据源。若是源数据尚未做为可查询类型出如今内存中,则 LINQ 提供程序必须以此方式表示源数据。例如,LINQ to XML 将 XML 文档加载到可查询的  XElement 类型中:
  //从 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.Customers
6         where cust.City == "London"
7         select cust;
Customers 表示数据库中的特定表

 

  1.3 查询

  查询指定要从数据源中检索的信息。 查询还能够指定在返回这些信息以前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。

  以前的示例中的查询是从整数数组中返回全部的偶数。 该查询表达式包含三个子句: fromwhere 和  select。(若是您熟悉 SQL,您会注意到这些子句的顺序与 SQL 中的顺序相反。) from 子句指定数据源, where 子句指定应用筛选器, select 子句指定返回的元素的类型。 目前须要注意的是,在 LINQ 中, 查询变量自己不执行任何操做而且不返回任何数据。 它只是存储在之后某个时刻执行查询时为生成结果而必需的信息。
 

  1.4 查询执行

  1.延迟执行

    如前所述,查询变量自己只是存储查询命令。  实际的查询执行会延迟到在 foreach 语句中循环访问查询变量时发生。 此概念称为“延迟执行”。

  2.强制当即执行

    对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。CountMaxAverage 和 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 numbers
5         where (num % 2) == 0
6         select num;
7 
8     var evenNumCount = evenNumQuery.Count();
View Code

 

  若要强制当即执行任意查询并缓存其结果,能够调用 ToList<TSource> 或 ToArray<TSource> 方法。

1     var numQuery2 =
2            (from num in numbers
3             where (num % 2) == 0
4             select num).ToList();
5 
6     var numQuery3 =
7           (from num in numbers
8            where (num % 2) == 0
9             select num).ToArray();
View Code

 

  此外,还能够经过在紧跟查询表达式以后的位置放置一个 foreach 循环来强制执行查询。可是,经过调用 ToList 或 ToArray,也能够将全部数据缓存在单个集合对象中。 

 

2、基本 LINQ 查询操做

  2.1 获取数据源:from

  在 LINQ 查询中,第一步是指定数据源。像在大多数编程语言中同样,必须先声明变量,才能使用它。在 LINQ 查询中,最早使用 from 子句的目的是引入数据源和范围变量。

1     //queryAllCustomers 是 IEnumerable<Cutsomer> 类型
2     //数据源 (customers) 和范围变量 (cust)
3     var queryAllCustomers = from cust in customers
4                                            select cust;
View Code

  范围变量相似于 foreach 循环中的迭代变量,但在查询表达式中,实际上不发生迭代。执行查询时,范围变量将用做对 customers 中的每一个后续元素的引用。由于编译器能够推断 cust 的类型,因此您没必要显式指定此类型。

 

  2.2 筛选:where

  也许最经常使用的查询操做是应用布尔表达式形式的筛选器。此筛选器使查询只返回那些表达式结果为 true 的元素。使用 where 子句生成结果。实际上,筛选器指定从源序列中排除哪些元素。

1     var queryLondonCustomers = from cust in customers
2                                   where cust.City = "London"
3                                     select cust;
只返回地址位于伦敦的 customers。

  您可使用熟悉的 C# 逻辑 AND(&&)和 OR(||) 运算符来根据须要在 where 子句中应用任意数量的筛选表达式。 

where cust.City = "London" && cust.Name = "Devon"
若要只返回位于“伦敦”和姓名为“Devon”的客户
where cust.City = "London" || cust.Name = "Paris"
若要返回位于伦敦或巴黎的客户

 

  2.3 排序:orderby

  一般能够很方便地将返回的数据进行排序。orderby 子句将使返回的序列中的元素按照被排序的类型的默认比较器进行排序。

1     var queryLondonCustomers = from cust in customers
2                                where cust.City = "London"
3                                orderby cust.Name descending 
4                                select cust;
按 Name 属性对结果进行排序

  由于 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     }
您能够指定结果应按 City 分组,以便位于伦敦或巴黎的全部客户位于各自组中。

  在本例中,cust.City 是键。

  在使用 group 子句结束查询时,结果采用列表的列表形式。列表中的每一个元素是一个具备 Key 成员及根据该键分组的元素列表的对象。在循环访问生成组序列的查询时,您必须使用嵌套的 foreach 循环。外部循环用于循环访问每一个组,内部循环用于循环访问每一个组的成员。  

  若是您必须引用组操做的结果,可使用 into 关键字来建立可进一步查询的标识符。

1     //custQuery 是 IEnumable<IGrouping<string, Customer>> 类型
2     var custQuery = from cust in customers
3                     group cust by cust.City
4                     into custGroup
5                     where custGroup.Count() > 2
6                     orderby custGroup.Key
7                     select custGroup;
这里的查询只返回那些包含两个以上的客户的组。

 

  2.5 联接:join

  联接运算建立数据源中没有显式建模的序列之间的关联。例如,您能够执行联接来查找位于同一地点的全部客户和经销商。在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。  

1     var innerJoinQuery = from cust in customers
2                        join dist in distributors on cust.City equals dist.City
3                        select new {CustomerName = cust.Name, DistributorName = dist.Name};
例如,您能够执行联接来查找位于同一地点的全部客户和经销商。

  在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。  

  在 LINQ 中,您没必要像在 SQL 中那样频繁使用 join,由于 LINQ 中的外键在对象模型中表示为包含项集合的属性。

    from order in Customer.Orders...
例如,Customer 对象包含 Order 对象的集合。没必要执行联接,只需使用点表示法访问订单。

  

  2.6 选择(投影):select

  select 子句生成查询结果并指定每一个返回的元素的“形状”或类型。

  例如,您能够指定结果包含的是整个 Customer 对象、仅一个成员、成员的子集,仍是某个基于计算或新对象建立的彻底不一样的结果类型。当 select 子句生成除源元素副本之外的内容时,该操做称为“投影”

 

3、使用 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 Teacher
13     {
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 students
56                             where student.City == "梦里"
57                             select student.Name)
58                             .Concat(from teacher in teachers
59                                     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 Customers
2                     select cust.City;

  

  2. 若要建立包含源元素的多个属性的元素,可使用具备命名对象或匿名类型的对象初始值设定项。

1     var query = from cust in Customer
2                    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 students
27                 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);
View Code

 

  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             }
View Code

   【备注】$"{radius * radius * 3.14}" 至关于 string.Format("{0}",radius * radius * 3.14),这里采用的是 C# 6.0 的语法。

 

4、LINQ 查询操做的类型关系

  LINQ 查询操做在数据源、查询自己及查询执行中是强类型的。查询中变量的类型必须与数据源中元素的类型和 foreach 语句中迭代变量的类型兼容。强类型能够保证在编译时捕获类型错误,以便及时改正。

 

  4.1 不转换源数据的查询

  下图演示不对数据执行转换的 LINQ to Objects 查询操做。源包含一个字符串序列,查询输出也是一个字符串序列。 

  ①数据源的类型参数决定范围变量的类型。

  ②选择的对象的类型决定查询变量的类型。此处的 name 为一个字符串。所以,查询变量是一个 IEnumerable<字符串>。  

  ③在 foreach 语句中循环访问查询变量。由于查询变量是一个字符串序列,因此迭代变量也是一个字符串。  

 

  4.2 转换源数据的查询

  下图演示对数据执行简单转换的 LINQ to SQL 查询操做。查询将一个 Customer 对象序列用做输入,并只选择结果中的 Name 属性。由于 Name 是一个字符串,因此查询生成一个字符串序列做为输出。  

  ①数据源的类型参数决定范围变量的类型。

  ②select 语句返回 Name 属性,而非完整的 Customer 对象。由于 Name 是一个字符串,因此 custNameQuery 的类型参数是 string,而非Customer。  

  ③由于 custNameQuery 是一个字符串序列,因此 foreach 循环的迭代变量也必须是 string

 

  下图演示另外一种转换。select 语句返回只捕获原始 Customer 对象的两个成员的匿名类型。

  ①数据源的类型参数始终为查询中的范围变量的类型。

  ②由于 select 语句生成匿名类型,因此必须使用 var 隐式类型化查询变量。

  ③由于查询变量的类型是隐式的,因此 foreach 循环中的迭代变量也必须是隐式的。

 

  4.3 让编译器推断类型信息

  您也可使用关键字 var,可用于查询操做中的任何局部变量。可是,编译器为查询操做中的各个变量提供强类型  

 

5、LINQ 中的查询语法和方法语法

  咱们编写的 LINQ 查询语法,在编译代码时,CLR 会将查询语法转换为方法语法。这些方法调用标准查询运算符的名称相似 WhereSelectGroupByJoinMax和 Average,咱们也是能够直接使用这些方法语法的。  

  查询语法和方法语法语义相同,可是,许多人员发现查询语法更简单、更易于阅读。某些查询必须表示为方法调用。例如,必须使用方法调用表示检索元素的数量与指定的条件的查询。还必须使用方法须要检索元素的最大值在源序列的查询。System.Linq 命名空间中的标准查询运算符的参考文档一般使用方法语法。

 

  5.1 标准查询运算符扩展方法

 1         static void Main(string[] args)
 2         {
 3             var nums = new int[4] { 1, 2, 3, 4 };
 4             
 5             //建立查询表达式
 6             var qureyNums = from n in nums
 7                             where n % 2 == 0
 8                             orderby n descending
 9                             select n;
10 
11             Console.WriteLine("qureyNums:");
12             foreach (var n in qureyNums)
13             {
14                 Console.WriteLine(n);
15             }
16     
17             //使用方法进行查询
18             var queryNums2 = nums.Where(n => n % 2 == 0).OrderByDescending(n => n);
19 
20             Console.WriteLine("qureyNums2:");
21             foreach (var n in queryNums2)
22             {
23                 Console.WriteLine(n);
24             }
25 
26             Console.Read();
27         }
下面的示例演示简单的查询表达式和编写为基于方法的查询的语义上等效的查询。

  两个示例的输出是相同的。您能够看到两种形式的查询变量的类型是相同的:IEnumerable<T>。  

  若要了解基于方法的查询,让咱们进一步地分析它。注意,在表达式的右侧,where 子句如今表示为对 numbers 对象的实例方法,在您从新调用该对象时其类型为 IEnumerable<int>。若是您熟悉泛型 IEnumerable<T> 接口,那么您就会了解,它不具备 Where 方法。可是,若是您在 Visual Studio IDE 中调用 IntelliSense 完成列表,那么您不只将看到 Where 方法,并且还会看到许多其余方法,如 SelectSelectManyJoin 和Orderby。下面是全部标准查询运算符。 

  尽管看起来 IEnumerable<T> 彷佛已被从新定义以包括这些附加方法,但事实上并不是如此。这些标准查询运算符都是做为“扩展方法”实现的。

 

  5.2 Lambda 表达式

  在前面的示例中,通知该条件表达式 (num % 2 == 0) 是做为内联参数Where 方法:Where(num => num % 2 == 0) 此内联表达式称为 lambda 表达式。将代码编写为匿名方法或泛型委托或表达式树是一种便捷的方法,不然编写起来就要麻烦得多。=> 是 lambda 运算符,可读为“goes to”。运算符左侧的 num 是输入变量,与查询表达式中的 num 相对应。编译器可推断 num 的类型,由于它了解 numbers 是泛型 IEnumerable<T> 类型。lambda 表达式与查询语法中的表达式或任何其余 C# 表达式或语句中的表达式相同;它能够包括方法调用和其余复杂逻辑。“返回值”就是表达式结果。  

 

  5.3 查询的组合性

  在上面的代码示例中,请注意 OrderBy 方法是经过在对 Where 的调用中使用点运算符来调用的。Where 生成筛选序列,而后 Orderby 经过对该序列排序来对它进行操做。由于查询会返回 IEnumerable,因此您可经过将方法调用连接在一块儿,在方法语法中将这些查询组合起来。这就是在您经过使用查询语法编写查询时编译器在后台所执行的操做。而且因为查询变量不存储查询的结果,所以您能够随时修改它或将它用做新查询的基础,即便在执行它后。

 

传送门

  入门:《走进 LINQ 的世界

  进阶:《LINQ 标准查询操做概述(强烈推荐)

  技巧:《Linq To Objects - 如何操做字符串》 和 《Linq To Objects - 如何操做文件目录

 

 


本文首联:http://www.cnblogs.com/liqingwen/p/5832322.html

【参考】https://msdn.microsoft.com/zh-cn/library/bb397897(v=vs.100).aspx 等

【来源】本文引用部分微软官方文档的图片

相关文章
相关标签/搜索