首页 > 代码库 > [C#] 开始使用 LINQ

[C#] 开始使用 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 。
 

目录

  • 介绍 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 }
View Code

技术分享

 

   下图显示了完整的查询操作。在 LINQ 中,查询的执行与查询本身截然不同;换句话说,查询本身指的是只创建查询变量,不检索任何数据。

技术分享

  

  1.2 数据源

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

  可查询类型不需要进行修改或特殊处理就可以用作 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.Customers6         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 numbers5         where (num % 2) == 06         select num;7 8     var evenNumCount = evenNumQuery.Count();
View Code

技术分享

 

  若要强制立即执行任意查询并缓存其结果,可以调用 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();
View Code

 

  此外,还可以通过在紧跟查询表达式之后的位置放置一个 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;
View Code

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

 

  2.2 筛选:where

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

技术分享
1     var queryLondonCustomers = from cust in customers2                                   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 customers2                                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 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...
例如,Customer 对象包含 Order 对象的集合。不必执行联接,只需使用点表示法访问订单。

  

  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);
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

技术分享

 

四、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)。这些方法调用标准查询运算符的名称类似 WhereSelectGroupByJoinMax和 Average。可以调用这些方法直接使用方法语法而不是查询语法。  

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

 

  

[C#] 开始使用 LINQ