欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

深入理解C#中的LINQ

最编程 2024-08-10 17:36:29
...

LINQ 查询简介TOC

LINQ 通过提供处理各种数据源和数据格式的数据的一致模型,简化了这一情况。 在 LINQ 查询中,始终会用到对象。 可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及 LINQ 提供程序可用的任何其他格式的数据。

查询操作的三个部分

所有 LINQ 查询操作都由以下三个不同的操作组成:

  1. 获取数据源
  2. 创建查询
  3. 执行查询。
    下面的示例演示如何用源代码表示查询操作的三个部分。 为方便起见,此示例将一个整数数组用作数据源;但其中涉及的概念同样适用于其他数据源。 本主题的其余部分也会引用此示例。
class IntroToLINQ
{
    static void Main()
    {
        // The Three Parts of a LINQ Query:
        // 1. Data source.
        int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

        // 2. Query creation.
        // numQuery is an IEnumerable<int>
        var numQuery =
            from num in numbers
            where (num % 2) == 0
            select num;

        // 3. Query execution.
        foreach (int num in numQuery)
        {
            Console.Write("{0,1} ", num);
        }
    }
}

下图演示完整的查询操作。 在 LINQ 中,查询的执行不同于查询本身。 换句话说,仅通过创建查询变量不会检索到任何数据。在这里插入图片描述

using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
    class Program
    {
        private static List<Customer> customers = new List<Customer>() {
             new Customer{ City="London",Name="Devon"},
            new Customer{ City="London",Name="Steve"} ,
            new Customer{ City="Paris",Name="Jane"}
        };
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
    public class Customer
    {
        public string Name { get; set; }
        public string City { get; set; }
    }
}

基本 LINQ 查询操作

在 LINQ 查询中,第一步是指定数据源。 和大多数编程语言相同,在使用 C# 时也必须先声明变量,然后才能使用它。 在 LINQ 查询中,先使用 from 子句引入数据源 (customers) 和范围变量 (cust)。

//queryAllCustomers is an IEnumerable<Customer>
var queryAllCustomers = from cust in customers
                        select cust;

范围变量就像 foreach 循环中的迭代变量,但查询表达式中不会真正发生迭代。 当执行查询时,范围变量将充当对 customers 中每个连续的元素的引用。 由于编译器可以推断 cust 的类型,因此无需显式指定它。 可通过 let 子句引入其他范围变量。
对于非泛型数据源(例如 ArrayList),必须显式键入范围变量。 有关详细信息,请参阅如何使用 LINQ (C#) 和 From 子句查询 ArrayList。

筛选 (where )

或许,最常见的查询操作是以布尔表达式的形式应用筛选器。 筛选器使查询仅返回表达式为 true 的元素。 将通过使用 where 子句生成结果。 筛选器实际指定要从源序列排除哪些元素。 在下列示例中,仅返回地址位于“London”的 customers。

var queryLondonCustomers = from cust in customers
                           where cust.City == "London"
                           select cust;

可使用熟悉的 C# 逻辑 AND 和 OR 运算符,在 where 子句中根据需要应用尽可能多的筛选器表达式。 例如,若要仅返回来自“London”的客户 AND 该客户名称为“Devon”,可编写以下代码:

where cust.City == "London" && cust.Name == "Devon"

要返回来自 London 或 Paris 的客户,可编写以下代码:

where cust.City == "London" || cust.City == "Paris"

中间件排序 (orderby)

对返回的数据进行排序通常很方便。 orderby 子句根据要排序类型的默认比较器,对返回序列中的元素排序。 例如,基于 Name 属性,可将下列查询扩展为对结果排序。 由于 Name 是字符串,默认比较器将按字母顺序从 A 到 Z 进行排序

var queryLondonCustomers3 =
    from cust in customers
    where cust.City == "London"
    orderby cust.Name ascending
    select cust;

要对结果进行从 Z 到 A 的逆序排序,请使用 orderby…descending 子句

分组(group)

group 子句用于对根据您指定的键所获得的结果进行分组。 例如,可指定按 City 对结果进行分组,使来自 London 或 Paris 的所有客户位于单独的组内。 在这种情况下,cust.City 是Key。

// queryCustomersByCity is an IEnumerable<IGrouping<string, Customer>>
  var queryCustomersByCity =
      from cust in customers
      group cust by cust.City;

  // customerGroup is an IGrouping<string, Customer>
  foreach (var customerGroup in queryCustomersByCity)
  {
      Console.WriteLine(customerGroup.Key);
      foreach (Customer customer in customerGroup)
      {
          Console.WriteLine("    {0}", customer.Name);
      }
  }

联接(join)

联接操作在不同序列间创建关联,这些序列在数据源中未被显式模块化。 例如,可通过执行联接来查找所有位置相同的客户和分销商。 在 LINQ 中,join 子句始终作用于对象集合,而非直接作用于数据库表。

var innerJoinQuery =
    from cust in customers
    join dist in distributors on cust.City equals dist.City
    select new { CustomerName = cust.Name, DistributorName = dist.Name };

选择(select)

select 子句生成查询结果并指定每个返回的元素的“形状”或类型。 例如,可以指定结果包含的是整个 Customer 对象、仅一个成员、成员的子集,还是某个基于计算或新对象创建的完全不同的结果类型。 当 select 子句生成除源元素副本以外的内容时,该操作称为投影。 使用投影转换数据是 LINQ 查询表达式的一种强大功能

标准查询运算符概述

标准查询运算符是组成 LINQ 模式的方法。 这些方法中的大多数都作用于序列;其中序列指其类型实现 IEnumerable 接口或 IQueryable 接口的对象。 标准查询运算符提供包括筛选、投影、聚合、排序等在内的查询功能。
共有两组 LINQ 标准查询运算符,一组作用于类型 IEnumerable 的对象,另一组作用于类型 IQueryable 的对象。 构成每个集合的方法分别是 Enumerable 和 Queryable 类的静态成员。 这些方法被定义为作为方法运行目标的类型的扩展方法。 可以使用静态方法语法或实例方法语法来调用扩展方法。
此外,多个标准查询运算符方法作用于那些基于 IEnumerable 或 IQueryable 的类型外的类型。 Enumerable 类型定义了两种这样的方法,这两种方法都作用于类型 IEnumerable 的对象。 这些方法(Cast(IEnumerable) 和 OfType(IEnumerable))均允许在 LINQ 模式中查询非参数化或非泛型集合。 这些方法通过创建一个强类型的对象集合来实现这一点。 Queryable 类定义了两种类似的方法 Cast(IQueryable) 和 OfType(IQueryable),这两种方法都作用于类型 Queryable 的对象。
各个标准查询运算符在执行时间上有所不同,具体情况取决于它们是返回单一值还是值序列。 返回单一实例值的这些方法(例如 Average 和 Sum)立即执行。 返回序列的方法会延迟查询执行,并返回一个可枚举的对象。
对于在内存中集合上运行的方法(即扩展 IEnumerable 的那些方法),返回的可枚举对象将捕获传递到方法的参数。 在枚举该对象时,将使用查询运算符的逻辑,并返回查询结果。
相反,扩展 IQueryable 的方法不会实现任何查询行为。 它们生成一个表示要执行的查询的表达式树。 源 IQueryable 对象执行查询处理。
可以在一个查询中将对查询方法的调用链接在一起,这就使得查询的复杂性可能会变得不确定。
下面的代码示例演示如何使用标准查询运算符来获取有关序列的信息。

string sentence = "the quick brown fox jumps over the lazy dog";  
// Split the string into individual words to create a collection.  
string[] words = sentence.Split(' ');  
  
// Using query expression syntax.  
var query = from word in words  
            group word.ToUpper() by word.Length into gr  
            orderby gr.Key  
            select new { Length = gr.Key, Words = gr };  
  
// Using method-based query syntax.  
var query2 = words.  
    GroupBy(w => w.Length, w => w.ToUpper()).  
    Select(g => new { Length = g.Key, Words = g }).  
    OrderBy(o => o.Length);  
  
foreach (var obj in query)  
{  
    Console.WriteLine("Words of length {0}:", obj.Length);  
    foreach (string word in obj.Words)  
        Console.WriteLine(word);  
}  
  
// This code example produces the following output:  
//  
// Words of length 3:  
// THE  
// FOX  
// THE  
// DOG  
// Words of length 4:  
// OVER  
// LAZY  
// Words of length 5:  
// QUICK  
// BROWN  
// JUMPS

查询表达式语法表

下表列出包含等效查询表达式子句的标准查询运算符。

方法 C# 查询表达式语法
Cast 使用显式类型化范围变量,例如:from int i in numbers
GroupBy group … by 或 group … by … into
GroupJoin join … in … on … equals … into …
Join join … in … on … equals …
OrderBy orderby
OrderByDescending orderby … descending
Select select
SelectMany 多个 from 子句。
ThenBy orderby …, …
ThenByDescending orderby …, … descending
Where where

对数据排序

方法
方法名 描述 C# 查询表达式语法
OrderBy 按升序对值排序。 orderby
OrderByDescending 按降序对值排序 orderby … descending
ThenBy 按升序执行次要排序 orderby …, …
ThenByDescending 按降序执行次要排序。 orderby …, … descending
Reverse 反转集合中元素的顺序。 不适用。
主要升序排序

下面的示例演示如何在 LINQ 查询中使用 orderby 子句按字符串长度对数组中的字符串进行升序排序。

string[] words = { "the", "quick", "brown", "fox", "jumps" };  
  
IEnumerable<string> query = from word in words  
                            orderby word.Length  
                            select word;  
  
foreach (string str in query)  
    Console.WriteLine(str);  
  
/* This code produces the following output:  
  
    the  
    fox  
    quick  
    brown  
    jumps  
*/
主要降序排序

下面的示例演示如何在 LINQ 查询中使用 orderby descending 子句按字符串的第一个字母对字符串进行降序排序

string[] words = { "the", "quick", "brown", "fox", "jumps" };  
  
IEnumerable<string> query = from word in words  
                            orderby word.Substring(0, 1) descending  
                            select word;  
  
foreach (string str in query)  
    Console.WriteLine(str);  
  
/* This code produces the following output:  
  
    the  
    quick  
    jumps  
    fox  
    brown  
*/
次要升序排序

下面的示例演示如何在 LINQ 查询中使用 orderby 子句对数组中的字符串执行主要和次要排序。 首先按字符串长度,其次按字符串的第一个字母,对字符串进行升序排序。

string[] words = { "the", "quick", "brown", "fox", "jumps" };  
  
IEnumerable<string> query = from word in words  
                            orderby word.Length, word.Substring(0, 1)  
                            select word;  
  
foreach (string str in query)  
    Console.WriteLine(str);  
  
/* This code produces the following output:  
  
    fox  
    the  
    brown  
    jumps  
    quick  
*/
次要降序排序

下面的示例演示如何在 LINQ 查询中使用 orderby descending 子句按升序执行主要排序,按降序执行次要排序。 首先按字符串长度,其次按字符串的第一个字母,对字符串进行排序。

string[] words = { "the", "quick", "brown", "fox", "jumps" };  
  
IEnumerable<string> query = from word in words  
                            orderby word.Length, word.Substring(0, 1) descending  
                            select word;  
  
foreach (string str in query)  
    Console.WriteLine(str);  
  
/* This code produces the following output:  
  
    the  
    fox  
    quick  
    jumps  
    brown  
*/

Set 运算

LINQ 中的集运算是指根据相同或不同集合(或集)中是否存在等效元素来生成结果集的查询运算。
下节列出了执行集运算的标准查询运算符方法。

方法名 描述
Distinct 删除集合中的重复值。
Except 返回差集,差集指位于一个集合但不位于另一个集合的元素。
Intersect 返回交集,交集指同时出现在两个集合中的元素 。
Union 返回并集,并集指位于两个集合中任一集合的唯一的元素。
Distinct

以下示例演示字符序列上 Enumerable.Distinct 方法的行为。 返回的序列包含输入序列的唯一元素。
在这里插入图片描述

string[] planets = { "Mercury", "Venus", "Venus", "Earth", "Mars", "Earth" };

IEnumerable<string> query = from planet in planets.Distinct()
                            select planet;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * Mercury
 * Venus
 * Earth
 * Mars
 */
Except

以下示例演示 Enumerable.Except 的行为。 返回的序列只包含位于第一个输入序列但不位于第二个输入序列的元素
在这里插入图片描述

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Except(planets2)
                            select planet;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * Venus
 */
Intersect

以下示例演示 Enumerable.Intersect 的行为。 返回的序列包含两个输入序列共有的元素。
在这里插入图片描述

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Intersect(planets2)
                            select planet;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * Mercury
 * Earth
 * Jupiter
 */
Union

以下示例演示对两个字符序列执行的联合操作。 返回的序列包含两个输入序列的唯一元素
在这里插入图片描述

string[] planets1 = { "Mercury", "Venus", "Earth", "Jupiter" };
string[] planets2 = { "Mercury", "Earth", "Mars", "Jupiter" };

IEnumerable<string> query = from planet in planets1.Union(planets2)
                            select planet;

foreach (var str in query)
{
    Console.WriteLine(str);
}

/* This code produces the following output:
 *
 * Mercury
 * Venus
 * Earth
 * Jupiter
 * Mars
 */

筛选数据 (Where/OfType)

筛选是指将结果集限制为仅包含满足指定条件的元素的操作。 它也称为选定内容。
下图演示了对字符序列进行筛选的结果。 筛选操作的谓词指定字符必须为“A”。
在这里插入图片描述

方法名 描述 C# 查询表达式语法
OfType 根据其转换为特定类型的能力选择值 不适用。
Where 选择基于谓词函数的值。 where
示例
string[] words = { "the", "quick", "brown", "fox", "jumps" };  
  
IEnumerable<string> query = from word in words  
                            where word.Length == 3  
                            select word;  
  
foreach (string str in query)  
    Console.WriteLine(str);  
  
/* This code produces the following output:  
  
    the  
    fox  
*/

限定符运算

限定符运算返回一个 Boolean 值,该值指示序列中是否有一些元素满足条件或是否所有元素都满足条件。
下图描述了两个不同源序列上的两个不同限定符运算。 第一个运算询问是否有一个或多个元素为字符“A”,结果为 true。 第二个运算询问是否所有元素都为字符“A”,结果为 true。
在这里插入图片描述

方法名 描述 C# 查询表达式语法
All 确定是否序列中的所有元素都满足条件 不适用。
Any 确定序列中是否有元素满足条件。 不适用。
Contains 确定序列是否包含指定的元素。 不适用。
All

以下示例使用 All 检查所有字符串是否为特定长度。

class Market
{
    public string Name { get; set; }
    public string[] Items { get; set; }
}

public static void Example()
{
    List<Market> markets = new List<Market>
    {
        new Market { Name = "Emily's", Items = new string[] { "kiwi", "cheery", "banana" } },
        new Market { Name = "Kim's", Items = new string[] { "melon", "mango", "olive" } },
        new Market { Name = "Adam's", Items = new string[] { "kiwi", "apple", "orange" } },
    };

    // Determine which market have all fruit names length equal to 5
    IEnumerable<string> names = from market in markets
                                where market.Items.All(item => item.Length == 5)
                                select market.Name;

    foreach (string name in names)
    {
        Console.WriteLine($"{name} market");
    }

    // This code produces the following output:
    //
    // Kim's market
}
Any

以下示例使用 Any 检查所有字符串是否以“o”开头。

class Market
{
    public string Name { get; set; }
    public string[] Items { get; set; }
}

public static void Example()
{
    List<Market> markets = new List<Market>
    {
        new Market { Name = "Emily's", Items = new string[] { "kiwi", "cheery", "banana" } },
        new Market { Name = "Kim's", Items = new string[] { "melon", "mango", "olive" } },
        new Market { Name = "Adam's", Items = new string[] { "kiwi", "apple", "orange" } },
    };

    // Determine which market have any fruit names start with 'o'
    IEnumerable<string> names = from market in markets
                                where market.Items.Any(item => item.StartsWith("o"))
                                select market.Name;

    foreach (string name in names)
    {
        Console.WriteLine($"{name} market");
    }

    // This code produces the following output:
    //
    // Kim's market
    // Adam's market
}
Contains

以下示例使用 Contains 检查所有数组是否具有特定元素。

class Market
{
    public string Name { get; set; }
    public string[] Items { get; set; }
}

public static void Example()
{
    List<Market> markets = new List<Market>
    
						

上一篇: 组连接

下一篇: 使用LINQ对多列进行分组并求和