深入理解C#(第3版)-- 【C#3】第12章 超越集合的LINQ(学习笔记)
12.1 使用LINQ to SQL 查询数据库
12.1.1 数据库和模型
1. 创建数据库架构
2. 创建实体类
12.1.2 用查询表达式访问数据库
1. 第1 个查询:查找分配给Tim 的缺陷
代码清单12-1 查询数据库以找出 Tim 的全部未解决缺陷
using (var context = new DefectModelDataContext()) { context.Log = Console.Out; User tim = context.Users .Where(user => user.Name == "Tim Trotter") .Single(); var query = from defect in context.Defects where defect.Status != Status.Closed where defect.AssignedTo == tim select defect.Summary; foreach (var summary in query) { Console.WriteLine(summary); } }
context.Log = Console.Out; 让数据上下文将所有执行的SQL 命令都输出到了控制台上
内存中的查询和LINQ to SQL 查询的唯一区别是数据源
2. 为更复杂的查询生成SQL :let 子句
12.1.3 包含连接的查询
1. 显式连接:使用通知订阅来匹配缺陷
2. 隐式连接:显示缺陷概要和项目名称
12.2 用IQueryable 和IQueryProvider 进行转换
12.2.1 IQueryable<T> 和相关接口的介绍
IQueryable<T>是从IEnumerable<T>和非泛型的IQueryable继承而来,而IQueryable又继承于非泛型的IEnumerable 。

理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列。从LINQ的角度看,由于是通过IQueryable的Expression属性返回结果,所以查询的详细信息就保存于表达式树中。
12.2.2 模拟接口实现来记录调用
12.2.3 把表达式粘合在一起:Queryable的扩展方法
首先,Enumerable的方法都使用委托作为参数,而Queryable的方法都使用表达式树作为参数。
Lambda表达式既可以被转换为委托实例,也可以转换为表达式树。
LINQ to SQL涉及的4 个关键元素都是C# 3的新特性:Lambda表达式、将查询表达式转换为使用Lambda表达式的“普通”表达式、扩展方法和表达式树。
查询表达式选择的两种路径,它们唯一的区别就是数据源实现的接口不同。

Lambda表达式既能被转换为委托实例也能被转换为表达式树,这样就有可能得到完全不同的实现:左边路径用于执行内存中的操作,而右边路径用来在数
据库上执行SQL 。
图12-3 这里只是为了加深印象,实际上是使用Enumerable还是Queryable在C#编译器中并没有明显的支持。它们并不是仅有的两条路径,稍后我们还会看到并行LINQ和响应式LINQ。
12.2.4 模拟实际运行的查询提供器
并不是所有的查询表达式都生成序列——如果你使用Sum 、Count或Average这样的聚合操作符,就不再真正创建一个“数据源”——我们会立刻对结果进行计算。
12.2.5 包装IQueryable
12.3 LINQ友好的API 和LINQ to XML
12.3.1 LINQ to XML 中的核心类型
LINQ to XML 位于System.Xml.Linq程序集,并且大多数类型都位于System.Xml.Linq命名空间。该命名空间下几乎所有类型都以X为前缀;普通DOM API中XmlElement类型在LINQ to XML中对应的是XElement 。
XName表示元素和特性的名称。创建实例时,通常使用字符串的隐式转换(这时不需要使用命名空间)或重载的+(XNamespace, string) 操作符。
XNamespace表示XML命名空间,通常是一个URI。创建其实例时常常使用字符串的隐式转换。
XObject是XNode 和XAttribute的共同父类:与在DOM API中不同,在LINQ to XML 中特性不是节点。如果某方法返回子节点的元素,这里面是不包含特性的。
XNode表示XML树中的节点。它定义了各种用于操作和查询树的成员。XNode 还有很多子类没有在图12-4 中列出,如XComment 和XDeclaration。它们相对来说并不常用,文档、元素和文本才是最常用的节点类型。
XAttribute表示包含名/ 值对的特性。值从本质上来说是文本,但可以显式转换成其他数据类型,如int 和DateTime 。
XContainer是XML树中可以包含子内容(主要为元素或文档)的节点。
XText表示文本节点,其派生类XCData 表示CDATA文本节点。(CDATA节点大致相当于逐字的字符串字面量,不需要任何转义。)XText很少直接在用户代码中实例化,当将字符串用于元素或文档的内容时,会将其转换为XText实例。
XElement 表示元素。它和XAttribute是LINQ to XML 中最常用的类。与在DOM API中不同,在创建一个XElement 时,不需要创建包含它的文档。如果你不是确实需要一个文档对象(如自定义XML声明),只用元素就可以了。
XDocument表示文档。可以通过Root属性访问其根元素,相当于XmlDocument.Document Element。如前所述,你并不总是需要创建一个文档。
12.3.2 声明式构造
构造内嵌的元素可以使代码很自然地形成树形的层次结构。以下是LINQ to XML代码片段:
new XElement("root", new XElement("child", new XElement("grandchild", "text")), new XElement("other-child"));
<root> <child> <grandchild>text</grandchild> </child> <other-child /> </root>
代码清单12-7 从示例用户中创建元素
var users = new XElement("users", SampleData.AllUsers.Select(user => new XElement("user", new XAttribute("name", user.Name), new XAttribute("type", user.UserType))) ); Console.WriteLine(users);
// Output <users> <username Tim="" type="Tester" /> <username Tara="" type="Tester" /> <username Deborah="" type="Developer" /> <username Darren="" type="Developer" /> <username Mary="" type="Manager" /> <username Colin="" type="Customer" /> </users>
12.3.3 查询单个节点
代码清单12-9 显示XML结构中的用户
XElement root = XmlSampleData.GetElement(); var query = root.Element("users").Elements().Select(user => new { Name = (string) user.Attribute("name"), UserType = (string) user.Attribute("type") }); foreach (var user in query) { Console.WriteLine ("{0}: {1}", user.Name, user.UserType); }
12.3.4 合并查询操作符
12.3.5 与LINQ和谐共处
LINQ to XML 使用了如下三种方式与其他LINQ相适应。
在构造函数中消费序列。LINQ是刻意声明式语言,LINQ to XML 支持声明式地创建XML结构。
在查询方法中返回序列。这大概是数据访问API必须遵循的最为明显的步骤:查询结果应该轻而易举地返回IEumerable<T> 或实现了该接口的类。
扩展了可以对XML类型的序列所作的查询,这样可以让它们看上去更像是统一的查询API,尽管有些查询必须用于XML。
12.4 用并行LINQ代替LINQ to Objects
12.4.1 在单线程中绘制曼德博罗特集
代码清单12-10 单线程的曼德博罗特生成查询
var query = from row in Enumerable.Range(0, Height) from column in Enumerable.Range(0, Width) select ComputeIndex(row, column); return query.ToArray();
12.4.2 ParallelEnumerable 、ParallelQuery 和AsParallel
如何以并行查询开始呢?答案是调用AsParallel,它是ParallelEnumerable 中的扩展方法,扩展了IEnumerable<T>。
代码清单12-11 第一次尝试多线程的曼德博罗特生成查询
var query = from row in Enumerable.Range(0, Height) .AsParallel() from column in Enumerable.Range(0, Width) select ComputeIndex(row, column); return query.ToArray();
对并行查询排序需要在线程之间进行更多的调和,而并行的唯一目标就是为了改善性能,因此PLINQ 默认使用无序查询。
12.4.3 调整并行查询
幸运的是,可以避开这一点——你只需要使用AsOrdered扩展方法,强制对查询排序即可。
代码清单12-12 有序的多线程曼德博罗特查询
var query = from row in Enumerable.Range(0, Height) .AsParallel() .AsOrdered() from column in Enumerable.Range(0, Width) select ComputeIndex(row, column); return query.ToArray();
AsUnordered ——使有序查询变得无序;如果你只需要对查询的第一部分排序,该方法可以使后续部分更加高效。
WithCancellation ——在该查询中指定取消标记(cancellation token)。取消标记的使用贯穿了整个并行扩展,使任务以安全、可控的方式得以取消。
WithDegreeOfParallelism ——指定执行查询的最大并发任务数。如果你不想让你的机器疲于招架,或者对没有CPU 限制的查询提高使用的线程数量,这可以用来限制使用的线程数。
WithExecutionMode——强制查询按并行方式执行,即使并行LINQ认为单线程执行得更快。
WithMergeOptions ——可以改变对结果的缓冲方式:禁止缓冲可以尽量缩短第一条结果的返回时间,但却降低了总的效率;完全缓冲的效率最高,但在查询执行完毕之前,不会返回任何结果。默认情况下使用两者的折中方案。
12.5 使用LINQ to Rx 反转查询模型
目前我们看到的所有LINQ库都有一个共同点:所得到的数据为IEnumerable<T>。乍看上去,这似乎显而易见、不值一提——还会有其他选择吗?不过,如果我们是推数据,而不是拉数据呢?与数据消费者掌管一切不同,数据提供者处于主导地位,当新数据可用的时候,由数据消费者进行响应(react )。
http://mng.bz/R7ip,http://mng.bz/HCLP,http://channel9.msdn.com/tags/Rx/
12.5.1 IObservable<T>和IObserver<T>
在.NET 4 中这两个接口是IObservable<out T> 和IObserver<in T> ,分别表示IObservable 是协变的,IObserver是逆变的。
调用一个可观察对象(observable)的Subscribe,就像是对事件使用+=来注册处理程序一样。Subscribe返回的可处置(disposable)值会记住传入的观察者(observer):处置它就像对同一个处理程序使用-=一样。
12.5.2 简单的开始
代码清单12-13 初次接触IObservable<T>
var observable = Observable.Range(0, 10); observable.Subscribe(x => Console.WriteLine("Received {0}", x), e => Console.WriteLine("Error: {0}", e), () => Console.WriteLine("Finished"));
12.5.3 查询可观察对象
1. 过滤和投影
代码清单12-14 在LINQ to Rx 中使用过滤和投影
var numbers = Observable.Range(0, 10); var query = from number in numbers where number % 2 == 0 select number * number; query.Subscribe(Console.WriteLine);
2. 分组
代码清单12-15 对3取模并分组
var numbers = Observable.Range(0, 10); var query = from number in numbers group number by number % 3; query.Subscribe(group => group.Subscribe (x => Console.WriteLine("Value: {0}; Group: {1}", x, group.Key)));
3. 合并
LINQ to Rx提供了SelectMany的一些重载,其理念仍然与LINQ to Objects 相同:原始序列中的每一项都生成一个新的序列,最终的结果是所有这些新序列的组合。
代码清单12-16 用 SelectMany生成多个范围
var query = from x in Observable.Range(1, 3) from y in Observable.Range(1, x) select new { x, y }; query.Subscribe(Console.WriteLine);
4. 新引入的和不支持的
12.5.4 意义何在
12.6 扩展LINQ to Objects
LINQ最美妙的一点就是其可扩展性。
12.6.1 设计和实现指南
1. 单元测试
2. 检查参数
3. 优化
4. 文档
5. 尽量只迭代一次
6. 释放迭代器
7. 自定义比较器
12.6.2 示例扩展:选择随机元素
代码清单12-17 从序列中选择随机元素的扩展方法
public static T RandomElement<T>(this IEnumerable<T> source,Random random) { if (source == null) { thrownew ArgumentNullException("source"); } if (random == null) { thrownew ArgumentNullException("random"); } ICollection collection = source as ICollection; if (collection != null) { int count = collection.Count; if (count == 0) { throw new InvalidOperationException("Sequence was empty."); } int index = random.Next(count); return source.ElementAt(index); } using(IEnumerator<T> iterator = source.GetEnumerator()) { if (!iterator.MoveNext()) { throw new InvalidOperationException("Sequence was empty."); } int countSoFar = 1; T current = iterator.Current; while(iterator.MoveNext()) { countSoFar++; if (random.Next(countSoFar) == 0) { current = iterator.Current; } } return current; } }
浙公网安备 33010602011771号