4.1 实例程序简介
本章我们将创建一个新的实例程序:LinqBooks,一个个人图书分类系统。接下来我们将给出实例程序的最终目标以及程序的功能。随后将会介绍实例程序的对象模型以及数据库架构,这些将贯穿本书使用。我们还会给出一些用来配合程序运行的示例数据。
该实例程序的要求:
对象模型必须足够丰富,以便支持各种类型的LINQ查询;
需要对内存中的对象、XML文档以及关系型数据库这3种类型的数据进行处理。其中既要包含单独的处理,也包含若干类型数据的组合;
要支持ASP.NET网站以及Windows Form应用程序;
需要涉及对本地数据以及外部数据的查询;
LinqBooks的主要功能应该包括:
跟踪现有的图书;
存放用户对这些图书的想法与评论;
获取更多有关该图书的信息;
发布(导出)图书的信息以及用户的评论;
从技术角度考虑,本书中将要在LinqBooks中实现的功能包括:
查询、插入、更新位于本地数据库中的数据;
提供对本地以及第三方(例如Amazon和Google)数据库的搜索功能;
从Web站点导入图书的数据;
通过XML文档导入、导出数据;
为推荐图书创建RSS摘要。
4.2 用LINQ操作内存中的集合
LINQ to Objects是指用LINQ操作内存中对象的集合的方法。但这是什么意思呢?LINQ to Objects 支持哪些类型的集合呢? LINQ to Objects又能在这些集合上进行什么操作呢?
若想让集合类型能够和LINQ to Objects配合使用,那么只要确保其实现了IEnumerable<T>接口即可。不要忘记,实现了IEnumerable<T>接口的对象在LINQ中叫做序列。幸运的是在.NET框架中几乎所有的泛型类型的集合都实现了IEnumerable<T>接口。这也就意味着LINQ to Object天数就能够支持那些你早已熟悉的.NET2.0集合类型。
泛型列表
毫无疑问,我们在.NET2.0中最常用到的集合类型就是List<T>,LINQ to Objects对List<T>以及其他泛型列表提供了完善的支持。
下面列出了框架所提供的主要泛型列表:
System.Collections.Generic.List<T>
System.Collections.Generic.LinkedList<T>
System.Collections.Generic.Queue<T>
System.Collections.Generic.Stack<T>
System.Collections.Generic.HashSet<T>
System.Collections.ObjectModel.Collection<T>
System.ComponentModel.BindingList<T>
通过LINQ toObjects查询泛型列表
using System;
usingSystem.Collection.Generic;
using System.Linq;
using LinqInAction.LinqBooks.Common;
static classTestList
{
static void Main()
{
List<Book> books = newList<Book>(){
new Book{Title="LINQin Action"},
new Book{Title="LINQfor Fun"},
newBook{Title="Extreme LINQ"}
};
var titles = books.Where(book=>book.Title.Contains("Action"))
.Select(book=>book.Title);
ObjectDumper.Write(titles);
}
}
泛型字典
与泛型列表类似的是,所有的泛型字典都支持用LINQ to Objects进行查询:
System.Collections.Generic.Dictionary<TKey,TValue>
System.Collections.Generic.SortedDictionary<TKey,TValue>
System.Collections.Generic.SortedList<TKey,TValue>
泛型字典均实现了Ienumerable<KeyValuePair<Tkey,TValue>>接口,其中的KeyValuePair结构包含了强类型的Key和Value属性。
字符串
虽然第一眼看上去System.String并不像是个集合,不过它的确实现了Ienumerable<Char>
接口。因此自然可以使用LINQ to Objects对其进行操作----就像操作别的集合类型一样。
其他集合
这里我们只列出了.NET框架内建的集合类型。不过,你也可以让LINQ to Objects与其他任何的实现了Ienumerable<T>接口的集合配合使用。也就是说,LINQ to Objects能够支持我们自定义的,或是其他框架中的集合类型。
一个可能会遇到的问题就是,不是所有的.NET集合类型都实现了Ienumberable<T>接口。事实上,只有支持强类型的集合才实现了该Ienumerable<T>接口。数组、泛型列表以及反省字典都是强类型的,因此我们可以在例如整型数组、字符串列表或包含了Book对象的字典中使用LINQ to Objects。
非凡性的集合类型没有实现Ienumerable<T>,不过却实现了Ienumerable接口。例如DataSet或ArrayList等非泛型集合。我们可以通过Cast和OfType查询操作符对这些非泛型集合类型进行查询。
4.4常用的标准查询操作符
按照操作类型分组的标准查询操作符
类型 查询操作符
过滤 OfType,Where
投影 Select,SelectMany
分区 Skip,SkipWhile,Take,TakeWhile
连接 GroupJoin,Join
串联 Concat
排序 OrderBy,OrderByDescending,Reverse,ThenBy,ThenByDescending
分组 GroupBy,ToLookup
集合 Distinct,Except,Intersect,Union
转换 AsEnumerable,AsQueryable,Cast,ToArray,ToDictionary,ToList
等同 SequenceEqual
元素 ElementAt,ElementAtOrDefault,First,FirstOrDefault,Last,LastOrDefault,Single,SingleOrDefault
生成 DefaultIfEmpty,Empty,Range,Repeat
数量 All,Any,Contains
聚集 Aggregate,Average,Count,LongCount,Max,Min,Sum
4.4.4 转换操作符
之所以LINQ提供了转换操作符,是为了能够将查询的返回序列转换为其他累心的集合。例如 ToArray能够将序列转换为数组,ToList能够将序列转换为列表等。在将LINQ查询与现有代码库进行集成时,这类转换操作符将非常有用----毕竟很多现有的类库只能够接受“传统”的集合类型。
ToArray和ToList也能够帮助我们立即得到查询的结果。在调用时,这些操作符将完整地遍历原序列,以便得到构造集合对象所必须的所有数据。
4.5 用不同的方式显示内存中的集合
4.5.1 排序
将图书按照出版社名称排序,然后按照价格从低到高排序,最后按照书名排序
from book insampleData.Books
orderby book.Publisher.Name,book.Pricedescending,book.Title
select new {Publisher=book.publisher.Name,
book.Price,book.Title};
4.5.4 使用连接
了解了通过嵌套查询和分组操作符将数据分组的方法之后,我们将介绍能够实现类似结果的另外一种途径,即连接操作符。连接操作符允许我们执行与投影、嵌套查询或分组类似的操作,不过其优势在于它的语法与SQL更加接近。
1. 组连接
在介绍连接操作符之前,我们先来看一个使用了join子句的查询表达式。
from publisher inSmapleData.Publishers
join book inSampleData.Books
on publisher equalsbook.Publisher into publisherBooks
select new {Publisher=publihser.Name,Books=publihserBooks };
上述代码就是所谓的“组连接”。它将每个出版社的图书分组为publisherBooks,并绑定到了一起。
2.内连接
内连接皆在找到两个序列的交集。经过内连接操作之后,满足指定条件且分别来自两个序列中的元素将被组合到一起,并组成一个新的序列。
Join操作符将基于元素中Key的匹配情况,实现内连接。例如,Join操作符可用来显示一个扁平状的出版社和图书信息。
from publisher inSampleData.Publishers join book in SampleData.Books
on publisher equalsbook.Publisher into publisherBooks
from book inpublihserBooks.DefaultifEmpty()
select new {
Publihser=publisher.Name,
Book=book==default(Book)?"(nobooks)":book.Title
};
2. 左外连接
正如我们前面看到的那样,在内连接中,只有在两个待连接序列中均符合条件的组合才会出现哎结果序列中。不过若是需要保留外部序列中的所有元素,而不管其是否有与之对应的、符合条件的内部序列元素存在,那么我们要执行一次左外连接操作。
除了能够确保左侧序列中的元素均至少在结果序列中出现一次(无论其是否能够与右侧序列中的元素相匹配)之外,左外连接与内连接基本相同。
用组连接实现一个左外连接
from publisher inSampleData.Publishers
join book inSampleData.Books
on publisher equalsbook.Publisher into publisherBooks
from book inpublisherBooks.DefaultIfEmpty()
select new {
Publisher=publisher.Name,
Book=book==default(Book)?"(nobooks)":book.Title
};
3. 交叉连接
交叉连接将计算出两个序列中所有元素的笛卡尔积。结果序列将由一个序列中的每个元素和另一个序列中每个元素的完全组合构成。结果序列中元素的个数为两个原序列中元素个数的积。
在介绍如何实现交叉连接之前,必须指明一点是LINQ中的交叉连接并不是通过Join操作符实现的。在LINQ的术语中,交叉连接是指一类投影操作,可以通过SelectMany操作符或在查询表达式中使用多个from子句来实现,这两种实现方式均在以前介绍过。
from publisher inSampleData.Publishers
from book inSampleData.Books
select new {
Correct = (publisher==book.Publisher),
Publisher = publisher.Name,
Book = book.Title
};
若不使用查询表达式,我们仍可通过SelectMany以及Select操作符得到同样的结果:
SampleData.Publishers.SelectMany(
publisher=>SampleData.Books.Select(
book=>new{
Correct =(publisher==book.Publisher),
publisher=publisher.Name,
Book=book.Title
}));
4.5.5 分区
Skip和Take 若想只得到序列中的一部分元素,那么可以使用Skip和Take操作符。Skip操作符将跳过序列中指定个数的元素,随后依次返回其余的元素。而Take操作符则会从序列的第一个元素开始,返回指定个数的元素。因此在页码为n且指定了每一页中元素个数为pageSize时,分页的计算公式即为sequence.Skip(n*pageSize).Take(pageSize)。
小结
作为介绍LINQ toObject的第一章,本章着重介绍了在内存中的数据上执行某些操作的方法。本章中也引入了LinqBooks实例程序,我们将在随后的章节中继续使用该实例程序。本章还演示了在ASP.NET Web站点和Windows Forms 应用程序汇总使用LINQ的方法。此外,本章还介绍了最为重要的几个标准查询操作符,并给出了使用这些查询操作符进行过滤、分组和排序的方法。
本章中介绍的内容对于LINQ to Objects而言非常重要。而这些知识也能够使用在其他的LINQ功能中。本书的第三和第四部分就将介绍这些操作在LINQ to SQL和LINQ to XML中的使用方法。