深入理解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;
    }
}
posted @ 2019-10-18 00:31  FH1004322  阅读(158)  评论(0)    收藏  举报