LINQ 查询简介TOC
LINQ 通过提供处理各种数据源和数据格式的数据的一致模型,简化了这一情况。 在 LINQ 查询中,始终会用到对象。 可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及 LINQ 提供程序可用的任何其他格式的数据。
查询操作的三个部分
所有 LINQ 查询操作都由以下三个不同的操作组成:
- 获取数据源
- 创建查询
- 执行查询。
下面的示例演示如何用源代码表示查询操作的三个部分。 为方便起见,此示例将一个整数数组用作数据源;但其中涉及的概念同样适用于其他数据源。 本主题的其余部分也会引用此示例。
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>
{
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 contains fruit names equal 'kiwi'
IEnumerable<string> names = from market in markets
where market.Items.Contains("kiwi")
select market.Name;
foreach (string name in names)
{
Console.WriteLine($"{name} market");
}
// This code produces the following output:
//
// Emily's market
// Adam's market
}
投影运算
投影是指将对象转换为一种新形式的操作,该形式通常只包含那些将随后使用的属性。 通过使用投影,您可以构造从每个对象生成的新类型。 可以投影属性,并对该属性执行数学函数。 还可以在不更改原始对象的情况下投影该对象。
方法名 | 描述 | C# 查询表达式语法 |
---|---|---|
Select | 投影基于转换函数的值。 | select。 |
SelectMany | 投影基于转换函数的值序列,然后将它们展平为一个序列 | 使用多个 from 子句 |
Select
下面的示例使用 select 子句来投影字符串列表中每个字符串的第一个字母。
List<string> words = new List<string>() { "an", "apple", "a", "day" };
var query = from word in words
select word.Substring(0, 1);
foreach (string s in query)
Console.WriteLine(s);
/* This code produces the following output:
a
a
a
d
*/
SelectMany
下面的示例使用多个 from 子句来投影字符串列表中每个字符串中的每个单词。
List<string> phrases = new List<string>() { "an apple a day", "the quick brown fox" };
var query = from phrase in phrases
from word in phrase.Split(' ')
select word;
foreach (string s in query)
Console.WriteLine(s);
/* This code produces the following output:
an
apple
a
day
the
quick
brown
fox
*/
Select() 和 SelectMany()比较
下面的示例比较 Select() 和 SelectMany() 的行为。 代码通过从源集合的每个花卉名称列表中提取前两项来创建一个“Flowers”。 此示例中,transform 函数 Select<TSource,TResult>(IEnumerable, Func<TSource,TResult>) 使用的“单值”本身即是值的集合。 这需要额外的 foreach 循环,以便枚举每个子序列中的每个字符串。
class Bouquet
{
public List<string> Flowers { get; set; }
}
static void SelectVsSelectMany()
{
List<Bouquet> bouquets = new List<Bouquet>() {
new Bouquet { Flowers = new List<string> { "sunflower", "daisy", "daffodil", "larkspur" }},
new Bouquet{ Flowers = new List<string> { "tulip", "rose", "orchid" }},
new Bouquet{ Flowers = new List<string> { "gladiolis", "lily", "snapdragon", "aster", "protea" }},
new Bouquet{ Flowers = new List<string> { "larkspur", "lilac", "iris", "dahlia" }}
};
// *********** Select ***********
IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);
// ********* SelectMany *********
IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);
Console.WriteLine("Results by using Select():");
// Note the extra foreach loop here.
foreach (IEnumerable<String> collection in query1)
foreach (string item in collection)
Console.WriteLine(item);
Console.WriteLine("\nResults by using SelectMany():");
foreach (string item in query2)
Console.WriteLine(item);
/* This code produces the following output:
Results by using Select():
sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia
Results by using SelectMany():
sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia
*/
}
数据分区
LINQ 中的分区是指将输入序列划分为两个部分的操作,无需重新排列元素,然后返回其中一个部分。
下图显示对字符序列进行三种不同的分区操作的结果。 第一个操作返回序列中的前三个元素。 第二个操作跳过前三个元素,返回剩余元素。 第三个操作跳过序列中的前两个元素,返回接下来的三个元素。
运算符名称 | 描述 | C# 查询表达式语法 |
---|---|---|
Skip | 跳过序列中指定位置之前的元素。 | 不适用。 |
SkipWhile | 基于谓词函数跳过元素,直到元素不符合条件。 | 不适用 |
Take | 获取序列中指定位置之前的元素。 | 不适用。 |
TakeWhile | 基于谓词函数获取元素,直到元素不符合条件。 | 不适用。 |
Join 操作
联接两个数据源就是将一个数据源中的对象与另一个数据源中具有相同公共属性的对象相关联。
当查询所面向的数据源相互之间具有无法直接领会的关系时,Join 就成为一项重要的运算。 在面向对象的编程中,这可能意味着在未建模对象之间进行关联,例如对单向关系进行反向推理。 下面是单向关系的一个示例:Customer 类有一个类型为 City 的属性,但 City 类没有作为 Customer 对象集合的属性。 如果你具有一个 City 对象列表,并且要查找每个城市中的所有客户,则可以使用联接运算完成此项查找。
LINQ 框架中提供的 join 方法包括 Join 和 GroupJoin。 这些方法执行同等联接,即根据 2 个数据源的键是否相等来匹配这 2 个数据源的联接。 (与此相较,Transact-SQL 支持除“等于”之外的联接运算符,例如“小于”运算符。)用关系数据库术语表达,就是说 Join 实现了内部联接,这种联接只返回那些在另一个数据集中具有匹配项的对象。 GroupJoin 方法在关系数据库术语中没有直接等效项,但实现了内部联接和左外部联接的超集。 左外部联接是指返回第一个(左侧)数据源的每个元素的联接,即使其他数据源中没有关联元素。
下图显示了一个概念性视图,其中包含两个集合以及这两个集合中的包含在内部联接或左外部联接中的元素.
运算符名称 | 描述 | C# 查询表达式语法 |
---|---|---|
Join | 根据键选择器函数 Join 两个序列并提取值对。 | join … in … on … equals … |
GroupJoin | 根据键选择器函数 Join 两个序列,并对每个元素的结果匹配项进行分组。 | join … in … on … equals … into … |
Join
下面的示例使用 join … in … on … equals … 子句基于特定值联接两个序列:
class Product
{
public string Name { get; set; }
public int CategoryId { get; set; }
}
class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
public static void Example()
{
List<Product> products = new List<Product>
{
new Product { Name = "Cola", CategoryId = 0 },
new Product { Name = "Tea", CategoryId = 0 },
new Product { Name = "Apple", CategoryId = 1 },
new Product { Name = "Kiwi", CategoryId = 1 },
new Product { Name = "Carrot", CategoryId = 2 },
};
List<Category> categories = new List<Category>
{
new Category { Id = 0, CategoryName = "Beverage" },
new Category { Id = 1, CategoryName = "Fruit" },
new Category { Id = 2, CategoryName = "Vegetable" }
};
// Join products and categories based on CategoryId
var query = from product in products
join category in categories on product.CategoryId equals category.Id
select new { product.Name, category.CategoryName };
foreach (var item in query)
{
Console.WriteLine($"{item.Name} - {item.CategoryName}");
}
// This code produces the following output:
//
// Cola - Beverage
// Tea - Beverage
// Apple - Fruit
// Kiwi - Fruit
// Carrot - Vegetable
}
GroupJoin
下面的示例使用 join … in … on … equals … into … 子句基于特定值联接两个序列,并对每个元素的结果匹配项进行分组
class Product
{
public string Name { get; set; }
public int CategoryId { get; set; }
}
class Category
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
public static void Example()
{
List<Product> products = new List<Product>
{
new Product { Name = "Cola", CategoryId = 0 },
new Product { Name = "Tea", CategoryId = 0 },
new Product { Name = "Apple", CategoryId = 1 },
new Product { Name = "Kiwi", CategoryId = 1 },
new Product { Name = "Carrot", CategoryId = 2 },
};
List<Category> categories = new List<Category>
{
new Category { Id = 0, CategoryName = "Beverage" },
new Category { Id = 1, CategoryName = "Fruit" },
new Category { Id = 2, CategoryName = "Vegetable" }
};
// Join categories and product based on CategoryId and grouping result
var productGroups = from category in categories
join product in products on category.Id equals product.CategoryId into productGroup
select productGroup;
foreach (IEnumerable<Product> productGroup in productGroups)
{
Console.WriteLine("Group");
foreach (Product product in productGroup)
{
Console.WriteLine($"{product.Name,8}");
}
}
// This code produces the following output:
//
// Group
// Cola
// Tea
// Group
// Apple
// Kiwi
// Group
// Carrot
}
对数据分组
分组是指将数据分到不同的组,使每组中的元素拥有公共的属性。
下图演示了对字符序列进行分组的结果。 每个组的键是字符。
运算符名称 | 描述 | C# 查询表达式语法 |
---|---|---|
GroupBy | 对共享通用属性的元素进行分组。 每组由一个 IGrouping<TKey,TElement> 对象表示。 | group … by或group … by … into … |
ToLookup | 将元素插入基于键选择器函数的 Lookup<TKey,TElement>(一种一对多字典)。 | 不适用 |
List<int> numbers = new List<int>() { 35, 44, 200, 84, 3987, 4, 199, 329, 446, 208 };
IEnumerable<IGrouping<int, int>> query = from number in numbers
group number by number % 2;
foreach (var group in query)
{
Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
foreach (int i in group)
Console.WriteLine(i);
}
/* This code produces the following output:
Odd numbers:
35
3987
199
329
Even numbers:
44
200
84
4
446
208
*/
Generation Operations
Generation 是指创建新的值序列。
运算符名称 | 描述 | C# 查询表达式语法 |
---|---|---|
DefaultIfEmpty | 用默认值单一实例集合替换空集合。 | 不适用。 |
Empty | 返回一个空集合。 | 不适用。 |
Range | 生成包含数字序列的集合。 | 不适用。 |
Repeat 生成包含一个重复值的集合。 | 不适用。 |
Equality Operations
两个序列,其相应元素相等且具有被视为相等的相同数量的元素。
运算符名称 | 描述 | C# 查询表达式语法 |
---|---|---|
SequenceEqual | 通过以成对方式比较元素确定两个序列是否相等。 | 不适用。 |
Element Operations (元素运算)
元素运算从序列中返回唯一、特定的元素。
运算符名称 | 描述 | C# 查询表达式语法 |
---|---|---|
ElementAt | 返回集合中指定索引处的元素。 | 不适用。 |
ElementAtOrDefault | 返回集合中指定索引处的元素;如果索引超出范围,则返回默认值。 | 不适用。 |
First | 返回集合的第一个元素或满足条件的第一个元素。 | 不适用。 |
FirstOrDefault | 返回集合的第一个元素或满足条件的第一个元素。 如果此类元素不存在,则返回默认值。 | 不适用。 |
Last | 返回集合的最后一个元素或满足条件的最后一个元素。 | 不适用 |
LastOrDefault | 返回集合的最后一个元素或满足条件的最后一个元素。 如果此类元素不存在,则返回默认值。 | 不适用。 |
Single | 返回集合的唯一一个元素或满足条件的唯一一个元素。 如果没有要返回的元素或要返回多个元素,则引发 InvalidOperationException。 | 不适用。 |
SingleOrDefault | 返回集合的唯一一个元素或满足条件的唯一一个元素。 如果没有要返回的元素,则返回默认值。 如果要返回多个元素,则引发 InvalidOperationException。 | 不适用 |
Converting Data Types ( 转换数据类型)
转换方法可更改输入对象的类型。
LINQ 查询中的转换运算可用于各种应用程序。 以下是一些示例:
- Enumerable.AsEnumerable 方法可用于隐藏类型的标准查询运算符自定义实现。
//Enumerable.AsEnumerable<TSource>(IEnumerable<TSource>) 方法
//public static System.Collections.Generic.IEnumerable<TSource> AsEnumerable<TSource> (this System.Collections.Generic.IEnumerable<TSource> source);
//下面的代码示例演示 AsEnumerable<TSource>(IEnumerable<TSource>) Where 当需要标准查询运算符实现时,如何使用隐藏类型的自定义方法。
// Custom class.
class Clump<T> : List<T>
{
// Custom implementation of Where().
public IEnumerable<T> Where(Func<T, bool> predicate)
{
Console.WriteLine("In Clump's implementation of Where().");
return Enumerable.Where(this, predicate);
}
}
static void AsEnumerableEx1()
{
// Create a new Clump<T> object.
Clump<string> fruitClump =
new Clump<string> { "apple", "passionfruit", "banana",
"mango", "orange", "blueberry", "grape", "strawberry" };
// First call to Where():
// Call Clump's Where() method with a predicate.
IEnumerable<string> query1 =
fruitClump.Where(fruit => fruit.Contains("o"));
Console.WriteLine("query1 has been created.\n");
// Second call to Where():
// First call AsEnumerable() to hide Clump's Where() method and thereby
// force System.Linq.Enumerable's Where() method to be called.
IEnumerable<string> query2 =
fruitClump.AsEnumerable().Where(fruit => fruit.Contains("o"));
// Display the output.
Console.WriteLine("query2 has been created.");
}
// This code produces the following output:
//
// In Clump's implementation of Where().
// query1 has been created.
//
// query2 has been created.
- Enumerable.OfType 方法可用于为 LINQ 查询启用非参数化集合。
//根据指定类型筛选 IEnumerable 的元素。
//public static System.Collections.Generic.IEnumerable<TResult> OfType<TResult> (this System.Collections.IEnumerable source);
System.Collections.ArrayList fruits = new System.Collections.ArrayList(4);
fruits.Add("Mango");
fruits.Add("Orange");
fruits.Add("Apple");
fruits.Add(3.0);
fruits.Add("Banana");
// Apply OfType() to the ArrayList.
IEnumerable<string> query1 = fruits.OfType<string>();
Console.WriteLine("Elements of type 'string' are:");
foreach (string fruit in query1)
{
Console.WriteLine(fruit);
}
// The following query shows that the standard query operators such as
// Where() can be applied to the ArrayList type after calling OfType().
IEnumerable<string> query2 =
fruits.OfType<string>().Where(fruit => fruit.ToLower().Contains("n"));
Console.WriteLine("\nThe following strings contain 'n':");
foreach (string fruit in query2)
{
Console.WriteLine(fruit);
}
// This code produces the following output:
//
// Elements of type 'string' are:
// Mango
// Orange
// Apple
// Banana
//
// The following strings contain 'n':
// Mango
// Orange
// Banana
3.Enumerable.ToArray、Enumerable.ToDictionary、Enumerable.ToList 和 Enumerable.ToLookup 方法可用于强制执行即时的查询,而不是将其推迟到枚举该查询时。
//从 IEnumerable<T> 中创建数组。
//public static TSource[] ToArray<TSource> (this System.Collections.Generic.IEnumerable<TSource> source);
class Package
{
public string Company { get; set; }
public double Weight { get; set; }
}
public static void ToArrayEx1()
{
List<Package> packages =
new List<Package>
{ new Package { Company = "Coho Vineyard", Weight = 25.2 },
new Package { Company = "Lucerne Publishing", Weight = 18.7 },
new Package { Company = "Wingtip Toys", Weight = 6.0 },
new Package { Company = "Adventure Works", Weight = 33.8 } };
string[] companies = packages.Select(pkg => pkg.Company).ToArray();
foreach (string company in companies)
{
Console.WriteLine(company);
}
}
/*
This code produces the following output:
Coho Vineyard
Lucerne Publishing
Wingtip Toys
Adventure Works
*/
Enumerable.ToDictionary 方法
方法名 | 描述 | C# 查询表达式语法 |
---|---|---|
AsEnumerable | 返回类型化为 IEnumerable 的输入。 | 不适用。 |
AsQueryable | 将(泛型)IEnumerable 转换为(泛型)IQueryable。 | 不适用。 |
Cast | 将集合中的元素转换为指定类型。 | 使用显式类型化的范围变量。 例如:from string str in words |
OfType | 根据其转换为指定类型的能力筛选值。 | 不适用。 |
ToArray | 将集合转换为数组。 此方法强制执行查询。 | 不适用。 |
ToDictionary | 根据键选择器函数将元素放入 Dictionary<TKey,TValue>。 此方法强制执行查询。 | 不适用。 |
ToList | 将集合转换为 List。 此方法强制执行查询。 | 不适用。 |
ToLookup | 根据键选择器函数将元素放入 Lookup<TKey,TElement>(一对多字典)。 此方法强制执行查询。 | 不适用。 |
示例
下面的代码示例使用显式类型化的范围变量将类型转换为子类型,然后才访问仅在此子类型上可用的成员。
class Plant
{
public string Name { get; set; }
}
class CarnivorousPlant : Plant
{
public string TrapType { get; set; }
}
static void Cast()
{
Plant[] plants = new Plant[] {
new CarnivorousPlant { Name = "Venus Fly Trap", TrapType = "Snap Trap" },
new CarnivorousPlant { Name = "Pitcher Plant", TrapType = "Pitfall Trap" },
new CarnivorousPlant { Name = "Sundew", TrapType = "Flypaper Trap" },
new CarnivorousPlant { Name = "Waterwheel Plant", TrapType = "Snap Trap" }
};
var query = from CarnivorousPlant cPlant in plants
where cPlant.TrapType == "Snap Trap"
select cPlant;
foreach (Plant plant in query)
Console.WriteLine(plant.Name);
/* This code produces the following output:
Venus Fly Trap
Waterwheel Plant
*/
}
Concat Operations (串联运算)
串联是指将一个序列附加到另一个序列的操作。
下图描绘了两个字符序列的串联操作。
方法名 | 描述 | C# 查询表达式语法 |
---|---|---|
Concat | 连接两个序列以组成一个序列。 | 不适用。 |
Aggregation Operations(集合运算)
聚合运算从值的集合中计算出单个值。 例如,从一个月累计的每日温度值计算出日平均温度值就是一个聚合运算。
下图显示对数字序列进行两种不同聚合操作所得结果。 第一个操作累加数字。 第二个操作返回序列中的最大值。
方法名 | 描述 | C# 查询表达式语法 |
---|---|---|
Aggregate | 不适用。 | |
Average | 不适用。 | |
Count | 不适用。 | |
LongCount | 不适用。 | |
Max | 不适用。 | |
Min | 不适用。 | |
Sum | 不适用。 |