使用 LINQ 查询






4.89/5 (126投票s)
最重要的 LINQ 查询解释。
目录
引言
Language INtegrated Queries(语言集成查询)是类似 SQL 的 C# 查询,可用于操作对象集合。在本文中,我将展示一些用例,说明如何使用 LINQ 查询对象集合。
本文的目标是成为 LINQ 的初学者指南,并为其他人提供参考/提醒。
背景
当人们听到 LINQ 时,大多数情况下,他们会想到类似 Entity Framework 的东西,即在 C# 代码中直接编写查询,这些查询将被直接转换为 SQL 语句并针对数据库执行。重要的是要知道这不是 LINQ。LINQ 是一组 C# 函数、类和运算符,使开发人员能够针对对象集合执行查询。真正的 LINQ 查询是针对集合执行的。
有许多 LINQ 扩展可以将查询转换为 SQL、XML/XPath、REST 等。在本文中,我将讨论基本的 LINQ 到集合查询。
LINQ 操作有两种形式——查询和函数。您可以在以下部分中查看有关它们的更多详细信息。
查询
在 LINQ 包中添加了一组预定义运算符(查询),使开发人员能够对对象集合创建类似 SQL 的查询。这些查询根据查询条件返回新的数据集合。查询使用以下形式
from <<element>> in <<collection>>
where <<expression>>
select <<expression>>
作为查询的结果,将返回一个泛型集合(IEnumerable<T>
)。泛型集合中的类型 <T>
是使用查询的 select
<<expression>>
部分中的表达式类型确定的。以下清单显示了一个 LINQ 查询示例,该查询返回价格低于 500 的书籍的书名
from book in myBooks
where book.Price < 500
select book.Title
此查询遍历书籍集合 myBooks
,从中选取价格属性小于 500 的书籍实体,并返回每个书名。查询的结果是一个 IEnumerable<String>
对象,因为 String
是 select
查询中返回表达式的类型(假设书籍的 Title
属性是 string
)。
函数
LINQ 添加了一组可应用于集合的有用函数。函数作为集合对象的新方法添加,可以使用以下形式
<集合对象>.方法名()
<集合对象>.方法名(<集合对象>)
<集合对象>.方法名(<<表达式>>)
所有 LINQ 查询都在 IEnumerable<T>
或 IQueryable<T>
类型的集合上执行,结果返回为新的集合 (IEnumerable<T>
)、对象或简单类型。返回集合的 LINQ 查询或函数的示例有
IEnumerable<T> myBooks = allBooks.Except(otherBooks);
IEnumerable<string> titles = myBooks.Where(book=>book.Price<500)
.Select(book=>book.Title);
int count = titles.Count();
这三个函数中的第一个函数创建一个新集合,其中所有书籍(除了 otherBooks
集合中的书籍)都被放置。第二个函数选取所有价格低于 500 的书籍,并返回它们的书名列表。第三个函数在 titles
集合中查找书名的数量。此代码展示了上面所示的三个 LINQ 函数的用法。
请注意,LINQ 查询和函数有两种形式。对于大多数查询,您可以编写等效的函数形式。示例如下所示
from book in myBooks
where book.Price < 500
select book.Title
| myBooks
.Where(book=>book.Price<500)
.Select(book=>book.Title);
|
在以下章节中,您可以找到更多关于 LINQ 到集合的示例。
动态 LINQ
如上所述,LINQ 查询和函数返回类或类集合。在大多数情况下,您将在查询的返回类型中使用现有领域类(Book
、Author
、Publisher
等)。但是,在某些情况下,您可能希望返回类模型中不存在的自定义类。例如,您可能只想返回书籍的 ISBN 和标题,因此您不想返回整个 Book
对象及其所有您根本不需要的属性。如果您想返回不同类中的字段(例如,如果您想返回书籍的标题和出版商的名称),则会出现类似的问题。
在这种情况下,您不需要定义只包含您想用作返回值的字段的新类。LINQ 允许您返回所谓的“匿名类”——动态创建的类,无需在您的类模型中显式定义。这种查询的一个示例如下所示
var items = from b in books
select new { Title: b.Title,
ISBN: b.ISBN
};
变量 items
是动态创建的类集合,其中每个对象都具有 Title
和 ISBN
属性。此未在您的类模型中明确定义,它是为此查询动态创建的。如果您尝试查找变量 items 的类型,您可能会看到类似 IEnumerable<a'>
的内容——.NET 框架为该类提供了一些动态名称(例如,a')。这样,您就可以只将临时类用于查询结果,而无需在代码中定义它们。
许多人认为这是一种不好的做法,因为我们在这里使用了没有类型的对象。这不是真的——items
变量确实有类型,但类型未在某个文件中定义。但是,您拥有类型化对象所需的一切
- 编译时语法检查——如果您在键入时犯了一些错误(例如,将 ISBN 拼写成 ISBN),编译器将显示警告并中止编译。
- 智能感知支持——Visual Studio 将像对待常规对象一样,向您显示对象的属性/方法列表。
但是,有一种方法可以在 .NET 中使用无类型对象。如果您将关键字 var
替换为关键字 dynamic
,则变量 items
将真正无类型。示例如下所示
dynamic items = from b in books
select new { Title: b.Title,
ISBN: b.ISBN
};
在这种情况下,您拥有一个真正的无类型对象——不会进行编译时检查(属性仅在运行时验证),并且您不会获得对动态对象的任何智能感知支持。
尽管 var
比 dynamic
好(尽可能使用 var
),但在某些情况下,您将被迫使用 dynamic
而不是 var
。例如,如果您想将某些 LINQ 查询的结果作为方法的返回值返回,则不能将方法的返回类型声明为 var
,因为匿名类的作用域在方法体中结束。在这种情况下,您将需要定义一个显式类或将返回值声明为 dynamic
。
在本文中,我将使用显式类或匿名类。
Lambda 表达式
在处理 LINQ 时,您会发现一些形式为 x => y
的“奇怪语法”。如果您不熟悉这种语法,我将简要解释一下。
在每个 LINQ 函数中,您都需要定义一些条件,这些条件将用于过滤对象。最自然的方法是传递一些将被评估的函数,如果对象满足函数中定义的条件,它将被包含在 LINQ 函数的结果集中。这种条件函数需要接受一个对象并返回一个 true
/false
值,该值将告诉 LINQ 是否应将此对象包含在结果集中。以下清单显示了检查书籍是否便宜的此类函数示例
public bool IsCheapBook(Book b)
{
return (b.Price < 10);
}
如果书价低于 10
,则它很便宜。现在有了这个条件,您可以在 LINQ 子句中使用它
var condition = new Func<Book, bool>(IsBookCheap);
var cheapBooks = books.Where(condition);
在这段代码中,我们以 Func<Book, bool>
的形式定义了一个指向函数 IsBookCheap
的“函数指针”,并将此函数传递给 LINQ 查询。LINQ 将对书籍集合中的每个书籍对象评估此函数,如果它满足函数中定义的条件,则在结果枚举中返回该书籍。
这并不是一种常见的做法,因为条件更具动态性,您不太可能在代码中的某个地方创建一组预编译函数,并且它们将被所有 LINQ 查询使用。通常,每个 LINQ 查询我们只需要一个表达式,因此最好动态生成并将条件传递给 LINQ。幸运的是,C# 允许我们使用 delegate
来实现这一点
var cheapBooks = books.Where(delegate(Book b){ return b.Price < 10; } );
在此示例中,我动态创建了 Function<Book, bool>
,并将其直接放入 Where()
条件中。结果与上一个示例相同,但您不需要为此定义一个单独的函数。
如果您认为对于一个简单的内联函数来说,键入的内容太多,则有一种更短的语法——lambda 表达式。首先,您可以看到我们不需要 delegate
关键字(编译器应该知道我们需要将 delegate
作为参数传递)。此外,为什么我们需要定义参数的类型(Book b
)?因为我们将此函数应用于书籍集合,我们知道 b
是 Book
——因此我们也可以删除此部分。此外,为什么我们要键入 return
——定义返回条件的表达式就足够了。我们唯一需要的是一个分隔符,它将放置在参数和将返回的表达式之间——在 C# 中,我们使用 =>
符号。
当我们删除所有不必要的东西并放置分隔符 =>
时,我们得到一个形式为 argument => expression
的 lambda 表达式语法。原始的 delegate
和等效的 lambda 表达式替换示例如下所示
Funct<Book, bool> delegateFunction = delegate(Book b){ return b.Price < 10; } ;
Funct<Book, bool> lambdaExpression = b => b.Price< 10 ;
如您所见,lambda 表达式只是内联函数的最小化语法。请注意,我们可以将 lambda 表达式用于任何类型的函数(不仅是返回 bool
值的函数)。例如,您可以定义一个 lambda 表达式,它接受一本书和一个作者,并以“书名 (作者姓名)”的格式返回书名。以下清单显示了这种 lambda 表达式的示例
Func<Book, Author, string> format = (book, author) => book.Title + "(" + author.Name + ")";
此函数将接受两个参数(Book
和 Author
),并返回一个 string
作为结果(Func<>
对象中的最后一个类型始终是返回类型)。在 lambda 表达式中定义了括号中的两个参数和将返回的字符串表达式。
Lambda 表达式在 LINQ 中被广泛使用,所以您应该习惯它们。
本文中使用的类模型
在示例中,我们将使用表示书籍、作者和出版商信息的结构。此数据结构的类图如下所示
每本书可以有多个作者和一个出版商。与实体相关的字段显示在图表上。Book
包含 ISBN、价格、页数、出版日期和标题信息。此外,它还包含对出版商的引用和对一组作者的引用。Author
具有名字和姓氏,没有对书籍的反向引用,出版商只有名称,没有对已出版书籍的引用。
假设书籍、出版商和作者的集合放置在 SampleData.Books
、SampleData.Publishers
和 SampleData.Authors
字段中。
LINQ 查询
在本节中,我将展示一些可以使用的基本查询/函数的示例。如果您是初学者,这应该是一个很好的起点。
基本查询
以下示例展示了 LINQ 的基本用法。为了使用 LINQ to Entities 查询,您需要有一个将被查询的集合(例如,书籍数组)。在这个基本示例中,您需要指定将查询哪个集合(“from <<element>> in <<collection>>”部分)以及查询中将选择哪些数据(“select <<expression>>”部分)。在下面的示例中,查询针对书籍数组执行,书籍实体被选择并作为查询结果返回。查询的结果是 IEnumerable<Book>
,因为“'select << expression>>”部分中表达式的类型是 Book
类。
Book[] books = SampleData.Books;
IEnumerable<Book> bookCollection = from b in books
select b;
foreach (Book book in bookCollection )
Console.WriteLine("Book - {0}", book.Title);
如您所见,此查询没有做任何有用的事情——它只是从书籍集合中选择所有书籍并将它们放入枚举中。但是,它展示了 LINQ 查询的基本用法。在以下示例中,您可以找到更多有用的查询。
字段的投影/选择
使用 LINQ,开发人员可以转换集合并创建新集合,其中元素只包含某些字段。以下代码显示了如何从书籍集合中提取书籍标题集合。
Book[] books = SampleData.Books;
IEnumerable<string> titles = from b in books
select b.Title;
foreach (string t in titles)
Console.WriteLine("Book title - {0}", t);
作为此查询的结果,创建了 IEnumerable<string>
,因为 select
部分中表达式的类型是 string
。以下代码显示了作为 select
函数和 lambda 表达式编写的等效示例
Book[] books = SampleData.Books;
IEnumerable<string> titles = books.Select(b=>b.Title);
foreach (string t in titles)
Console.WriteLine("Book title - {0}", t);
任何类型都可以用作结果集合类型。在以下示例中,返回一个匿名类枚举,其中枚举中的每个元素都包含对书籍和书籍第一位作者的引用
var bookAuthorCollection = from b in books
select new { Book: b,
Author: b.Authors[0]
};
foreach (var x in bookAuthorCollection)
Console.WriteLine("Book title - {0}, First author {1}",
x.Book.Title, x.Author.FirstName);
当您需要动态创建新类型的集合时,这种类型的查询非常有用。
在 Select 查询中返回扁平化集合
假设您想为一组书籍返回一个作者集合。使用 Select
方法,此查询将如下面示例所示
Book[] books = SampleData.Books;
IEnumerable< List<Author> > authors = books.Select(b=>b.Authors);
foreach (List<Author> bookAuthors in authors)
bookAuthors.ForEach(author=>Console.WriteLine("Book author {0}", author.Name);
在此示例中,从书籍集合中获取每本书的作者列表。当您使用 Select
方法时,它将在结果枚举中返回一个元素,并且每个元素都将具有 List<Author>
类型,因为这是 Select
方法中返回属性的类型。结果是,您需要对集合进行两次迭代才能显示所有作者——一次迭代枚举,然后对于枚举中的每个列表,您需要再次迭代以访问每个单独的作者。
然而,在某些情况下,这不是您想要的。您可能想要一个单一的扁平化作者列表,而不是两级列表。在这种情况下,您需要使用 SelectMany
而不是 Select
方法,如下面示例所示
Book[] books = SampleData.Books;
IEnumerable<Author> authors = books.SelectMany(b=>b.Authors);
foreach (Author authors in authors)
Console.WriteLine("Book author {0}", author.Name);
SelectMany
方法将 lambda 表达式中返回的所有集合合并到单个扁平化列表中。这样,您可以轻松地操作集合的元素。
请注意,在第一个示例中,当我迭代作者列表以显示它们时,我使用了 ForEach
方法。ForEach
方法不是 LINQ 的一部分,因为它是一个添加到列表类的常规扩展方法。然而,它对于紧凑的内联循环来说是一个非常有用的替代方案(这可能是许多人认为它是 LINQ 的一部分的原因)。由于 ForEach
方法不是 LINQ 的一部分,您不能像常规 LINQ 方法一样在枚举上使用它,因为它被定义为 List<T>
而不是 Enumerable<T>
的扩展——如果您喜欢此方法,您需要将您的可枚举对象转换为列表才能使用它。
实体排序
使用 LINQ,开发人员可以对集合中的实体进行排序。以下代码显示了如何获取书籍集合,按书籍出版商名称和标题对元素进行排序,并选择排序集合中的书籍。作为查询的结果,您将获得一个按书籍出版商和标题排序的 IEnumerable<Book>
集合。
Book[] books = SampleData.Books;
IEnumerable<Book> booksByTitle = from b in books
orderby b.Publisher.Name descending, b.Title
select b;
foreach (Book book in booksByTitle)
Console.WriteLine("Book - {0}\t-\tPublisher: {1} ",
book.Title, book.Publisher.Name );
使用函数的替代代码示例如下
Book[] books = SampleData.Books;
IEnumerable<Book> booksByTitle = books.OrderByDescending(book=>book.Publisher.Name)
.ThenBy(book=>book.Title);
foreach (Book book in booksByTitle)
Console.WriteLine("Book - {0}\t-\tPublisher: {1} ",
book.Title, book.Publisher.Name );
如果您有复杂的结构,需要使用相关实体的属性(在此示例中,书籍按出版商名称字段排序,该字段根本不在书籍类中)对实体进行排序,则这种类型的查询非常有用。
实体过滤/限制
使用 LINQ,开发人员可以从集合中过滤实体并创建只包含满足特定条件的新集合。以下示例创建了一个书籍集合,其中包含标题中包含单词“our
”且价格小于 500 的书籍。从书籍数组中选择标题包含单词“our
”、价格与 500 比较的记录,并将这些书籍选择并作为新集合的成员返回。在“''where <<expression>>''”中可以使用有效的 C# 布尔表达式,该表达式使用集合中的字段、常量和作用域中的变量(即价格)。返回集合的类型是 IEnumerable<Book>
,因为在“''select <<expression>>''”部分中选择的类型是 Book
。
Book[] books = SampleData.Books;
int price = 500;
IEnumerable<Book> filteredBooks = from b in books
where b.Title.Contains("our") && b.Price < price
select b;
foreach (Book book in filteredBooks)
Console.WriteLine("Book - {0},\t Price {1}", book.Title, book.Price);
作为替代,可以使用 .Where()
函数,如下面示例所示
Book[] books = SampleData.Books;
int price = 500;
IEnumerable<Book> filteredBooks = books.Where(b=> (b.Title.Contains("our")
&& b.Price < price) );
foreach (Book book in filteredBooks)
Console.WriteLine("Book - {0},\t Price {1}", book.Title, book.Price);
局部变量
您可以在 LINQ 查询中使用局部变量,以提高查询的可读性。局部变量是使用 LINQ 查询内部的 let <<localname>> = <<expression>>
语法创建的。一旦定义,局部变量可以在 LINQ 查询的任何部分(例如 where
或 select
子句)中使用。以下示例演示了如何使用局部变量选择标题中包含单词“our
”的书籍中的一组第一位作者。
IEnumerable<Author> firstAuthors = from b in books
let title = b.Title
let authors = b.Authors
where title.Contains("our")
select authors[0];
foreach (Author author in firstAuthors)
Console.WriteLine("Author - {0}, {1}",
author.LastName, author.FirstName);
在此示例中,变量 Title
和 Authors
引用书籍的标题和作者列表。通过变量引用这些项可能比直接引用更容易。
集合方法
使用 LINQ,您可以修改现有集合或使用其他 LINQ 查询创建的集合。LINQ 为您提供了一组可以应用于集合的函数。这些函数可以分为以下几种类型
- 集合函数 - 可用于集合操作的函数,例如合并、交集、反向排序等。
- 元素函数 - 可用于从集合中获取特定元素的函数
- 转换函数 - 用于将一种集合类型转换为另一种类型的函数
- 聚合函数 - 类似于 SQL 的函数,可用于查找集合中某个字段的最大值、总和或平均值
- 量词函数 - 用于快速遍历集合
这些函数在以下部分中描述。
集合函数
集合运算符允许您操作集合并使用标准集合操作,如并集、交集等。LINQ 集合运算符有
Distinct
- 用于从集合中提取不同的元素Union
- 创建一个表示两个现有集合并集的集合Concat
- 将一个集合中的元素添加到另一个集合Intersect
- 创建一个包含两个集合中都存在的元素的集合Except
- 创建一个包含在一个集合中存在但不存在于另一个集合中的元素的集合Reverse
- 创建一个元素顺序颠倒的集合副本EquallAll
- 检查两个集合是否具有相同顺序的相同元素Take
- 此函数从一个集合中获取一定数量的元素,并将它们放入一个新集合Skip
- 此函数跳过集合中的一定数量的元素
假设在前面的示例中创建了 booksByTitle
和 filteredBooks
集合,以下代码查找 booksByTitle
中不存在于 filteredBooks
中的所有书籍,并反转它们的顺序。
IEnumerable<Book> otherBooks = booksByTitle.Except(filteredBooks);
otherBooks = otherBooks.Reverse();
foreach (Book book in otherBooks)
Console.WriteLine("Other book - {0} ", book.Title);
在以下示例中,booksByTitle
和 filteredBooks
被连接,并显示元素数量和不同元素的数量。
IEnumerable<Book> mergedBooks = booksByTitle.Concat(filteredBooks);
Console.WriteLine("Number of elements in merged collection is {0}", mergedBooks.Count());
Console.WriteLine("Number of distinct elements in merged collection is {0}",
mergedBooks.Distinct().Count());
分页示例
此示例展示了使用 Skip(int)
和 Take(int)
方法进行客户端分页的示例。假设每页有十本书,使用 Skip(30)
跳过前三页(每页十本书,共三页),并使用 Take(10)
获取第四页上应显示的所有书籍。示例代码如下
IEnumerable<Book> page4 = booksByTitle.Skip(30).Take(10);
foreach (Book book in page4)
Console.WriteLine("Fourth page - {0} ", book.Title);
还有 Skip
/Take
函数在 SkipWhile
/TakeWhile
形式下的有趣用法
IEnumerable<Book> page1 = booksByTitle.OrderBy(book=>book.Price)
.SkipWhile(book=>book.Price<100)
.TakeWhile(book=>book.Price<200);
foreach (Book book in page1)
Console.WriteLine("Medium price books - {0} ", book.Title);
在此示例中,书籍按价格排序,所有价格低于 100 的书籍被跳过,所有价格低于 200 的书籍被返回。这样,所有价格在 100 到 200 之间的书籍都被找到了。
元素函数
当您需要从集合中提取特定元素时,可以使用几个有用的函数
First
- 用于查找集合中的第一个元素。可选地,您可以向此函数传递一个条件,以查找满足条件的第一个元素。FirstOrDefault
- 用于查找集合中的第一个元素。如果找不到此类元素,则返回该类型的默认元素(例如,0
或null
)。ElementAt
- 用于查找特定位置的元素。
以下示例展示了 FirstOrDefault
和 ElementAt
函数的用法
Book firstBook = books.FirstOrDefault(b=>b.Price>200);
Book thirdBook = books.Where(b=>b.Price>200).ElementAt(2);
请注意,您可以将函数应用于集合,也可以应用于其他 LINQ 函数的结果。
转换函数
有一些转换函数允许您将一种集合类型转换为另一种。其中一些函数是
ToArray
- 用于将IEnumerable<T>
集合的元素转换为<T>
元素数组ToList
- 用于将IEnumerable<T>
集合的元素转换为List<T>
列表ToDictionary
- 用于将集合的元素转换为Dictionary
。在转换过程中,必须指定键和值。OfType
- 用于提取实现接口/类T2
的IEnumerable<T1>
集合元素,并将它们放入IEnumerable<T2>
集合中
以下示例展示了 ToArray
和 ToList
函数的用法
Book[] arrBooks = books.ToArray();
List<Book> lstBook = books.ToList();
ToDictionary
是一个有趣的方法,它允许您通过某个字段快速索引列表。这种查询的一个示例如下所示
Dictionary<string, Book> booksByISBN = books.ToDictionary(book => book.ISBN);
Dictionary<string, double> pricesByISBN = books.ToDictionary( book => book.ISBN,
book=>book.Price);
如果您只提供一个 lambda 表达式,ToDictionary
将使用它作为新字典的键,而元素将是对象。您还可以为键和值提供 lambda 表达式,并创建自定义字典。在上面的示例中,我们创建了一个以 ISBN 键索引的书籍字典,以及一个以 ISBN 索引的价格字典。
量词函数
在每个集合中,您都可以找到一些逻辑函数,可用于快速遍历集合并检查某些条件。例如,您可以使用的一些函数是
Any
- 检查集合中是否有任何元素满足特定条件All
- 检查集合中所有元素是否满足特定条件
函数用法的示例如下所示
if(list.Any(book=>book.Price<500))
Console.WriteLine("At least one book is cheaper than 500$");
if(list.All(book=>book.Price<500))
Console.WriteLine("All books are cheaper than 500$");
在上面的示例中,All
和 Any
函数将检查列表中书籍的价格是否小于 500 的条件是否满足。
聚合函数
聚合函数使您能够对集合元素执行聚合。LINQ 中可用的聚合函数有 Count
、Sum
、Min
、Max
等。
以下示例显示了应用于整数数组的一些聚合函数的简单用法
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
Console.WriteLine("Count of numbers greater than 5 is {0} ", numbers.Count( x=>x>5 ));
Console.WriteLine("Sum of even numbers is {0} ", numbers.Sum( x=>(x%2==0) ));
Console.WriteLine("Minimum odd number is {0} ", numbers.Min( x=>(x%2==1) ));
Console.WriteLine("Maximum is {0} ", numbers.Max());
Console.WriteLine("Average is {0} ", numbers.Average());
如您所见,您可以使用标准聚合函数,也可以使用 lambda 条件预选一个子集。
高级查询
本节展示了如何创建高级查询。这类查询包括连接不同的集合和使用 group by
运算符。
表连接
LINQ 允许您对对象集合使用类似 SQL 的连接。集合的连接方式与 SQL 中的表相同。以下示例展示了如何连接三个集合(出版商、书籍和作者),在 where
部分设置一些限制条件,并打印查询结果
var baCollection = from pub in SampleData.Publishers
from book in SampleData.Books
from auth in SampleData.Authors
where book.Publisher == pub
&& auth.FirstName.Substring(0, 3) == pub.Name.Substring(0, 3)
&& book.Price < 500
&& auth.LastName.StartsWith("G")
select new { Book = book, Author = auth};
foreach (var ba in baCollection)
{ Console.WriteLine("Book {0}\t Author {1} {2}",
ba.Book.Title,
ba.Author.FirstName,
ba.Author.LastName);
}
此查询获取出版商、书籍和作者;通过出版商引用连接书籍和出版商,通过姓名的前三个字母连接作者和出版物。此外,结果通过价格小于 500 的书籍和姓名以字母“G”开头的作者进行过滤。如您所见,您可以使用任何条件连接集合实体。
Join 运算符
LINQ 允许您使用“''<<collection>> join <<collection>> on <<expression>>'' 运算符在 join
条件上连接两个集合。它类似于前面的示例,但您可以轻松阅读查询。以下示例展示了如何使用 Book.Publisher
引用作为 join
条件将出版商与他们的书籍连接起来。
var book_pub = from p in SampleData.Publishers
join b in SampleData.Books on p equals b.Publisher
into publishers_books
where p.Name.Contains("Press")
select new { Publisher = p, Books = publishers_books};
foreach (var bp in book_pub){
Console.WriteLine("Publisher - {0}", bp.Publisher.Name);
foreach (Book book in bp.Books)
Console.WriteLine("\t Book - {0}", book.Title);
}
一个书籍集合作为 publishers_books
属性附加到每个出版商记录。在 where
子句中,您可以通过条件过滤出版商。
请注意,如果您通过引用连接对象(在上面的示例中,您可以看到 join
条件是 p equals b.Publisher
),如果引用的对象未加载,则可能会收到“Object reference not set to the instance objects
”异常。请确保在开始查询之前已加载所有相关对象,确保已在查询中处理 null
值,或者在可能的情况下使用 ID
而不是引用作为 join
条件。
分组运算符
LINQ 允许您对对象集合使用分组功能。以下示例展示了如何按出版年份对书籍进行分组。作为查询的结果,返回的是一个匿名类的枚举,其中包含一个表示分组所用键的属性(Year
),以及另一个表示该年份出版书籍集合的属性(Books
)。
var booksByYear = from book in SampleData.Books
group book by book.PublicationDate.Year
into groupedByYear
orderby groupedByYear.Key descending
select new {
Value = groupedByYear.Key,
Books = groupedByYear
};
foreach (var year in booksByYear){
Console.WriteLine("Books in year - {0}", year.Value);
foreach (var b in year.Books)
Console.WriteLine("Book - {0}", b.Title);
}
聚合示例
使用 LINQ 和分组,您可以模拟“select title, count(*) from table
”的 SQL 查询。以下 LINQ 查询展示了如何使用 LINQ 聚合数据
var raw = new[] { new { Title = "first", Stat = 20, Type = "view" },
new { Title = "first", Stat = 12, Type = "enquiry" },
new { Title = "first", Stat = 0, Type = "click" },
new { Title = "second", Stat = 31, Type = "view" },
new { Title = "second", Stat = 17, Type = "enquiry" },
new { Title = "third", Stat = 23, Type = "view" },
new { Title = "third", Stat = 14, Type = "click" }
};
var groupeddata = from data in raw
group data by data.Title
into grouped
select new { Title = grouped.Key,
Count = grouped.Count()
};
foreach (var data in groupeddata){
Console.WriteLine("Title = {0}\t Count={1}", data.Title, data.Count);
}
嵌套查询
LINQ 允许您使用嵌套查询。一旦您从集合中选择了实体,您可以将它们用作内部查询的一部分,该内部查询可以在其他集合上执行。例如,您可以看到上面显示的类图,其中 Book
类引用了 Publisher
类,但没有反向关系。使用嵌套 LINQ 查询,您可以在集合中选择所有出版商,并为每个出版商实体调用其他 LINQ 查询,这些查询查找所有引用该出版商的书籍。此类查询的示例如下所示
var publisherWithBooks = from publisher in SampleData.Publishers
select new { Publisher = publisher.Name,
Books = from book in SampleData.Books
where book.Publisher == publisher
select book
};
foreach (var publisher in publisherWithBooks){
Console.WriteLine("Publisher - {0}", publisher.Name);
foreach (Book book in publisher.Books)
Console.WriteLine("\t Title \t{0}", book.Title);
}
当在查询中创建新实例时,对于每个出版商实体,都会获取 LINQ 查询中设置的书籍集合,并在控制台上显示。
使用局部变量,您可以为查询提供更好的格式,如下面示例所示
var publisherWithBooks = from publisher in SampleData.Publishers
let publisherBooks = from book in SampleData.Books
where book.Publisher == publisher
select book
select new { Publisher = publisher.Name,
Books = publisherBooks
};
foreach (var publisher in publisherWithBooks){
Console.WriteLine("Publisher - {0}", publisher.Name);
foreach (Book book in publisher.Books)
Console.WriteLine("\t Title \t{0}", book.Title);
}
在此查询中,当前出版商的书籍被放置在 publisherBooks
变量中,然后返回一个包含出版商名称及其书籍的对象。
通过这种方式,您可以动态地在原始类模型中不存在的实体之间创建新的关系。
结论
在实体集合上使用 LINQ 可以显著改善您的代码。一些常见的集合操作,如过滤、排序、查找最小值或最大值,可以使用单个函数调用或查询完成。此外,许多 LINQ 功能使您能够以类似 SQL 的方式使用集合,从而允许您像在标准 SQL 中一样连接和分组集合。如果没有 LINQ,对于这种功能,您可能需要创建几个复杂的函数,但现在有了 LINQ 库,您可以用一条语句完成它。
如果您对改进本文或 LINQ 查询的一些有趣用法有任何建议,请告诉我,我将在此处添加它们。
历史
- 2012年2月15日:初版