LINQ - 解释查询

4 、Interpreted Queries

LINQ提供了两种并行架构:

  • 本地对象集合的本地查询

  • 远程数据源的解释查询。

到目前为止,我们研究的都是本地查询的体系结构,它们操作在实现了 IEnumerable接口的序列上。 本地查询解析为可枚举类中的查询操作符(默认情况下),而这些操作符又可以被解析为修饰符序列链(实际就是委托链)。下面开始学习解释查询。

解释查询是描述性的

解释查询不对 IEnumerable接口实现的序列操作了,它们对实现iquerizable 接口的序列进行操作,并解析到Queryable类中的查询操作符,查询操作符发出在运行时解释的表达式树,而在本地查询中查询操作符编译为委托链。

Enumerable中的查询操作符实际上可以使用iquerizable 序列。困难在于生成的查询总是在客户机上本地执行——这就是可查询类中提供第二组查询操作符的原因。

There are two IQueryable implementations in the .NET Framework:

  • LINQ to SQL

  • Entity Framework (EF)

It’s also possible to generate an IQueryable wrapper around an ordinary enumerable collection by calling the AsQueryable method.

(翻译为汉语还有点别扭)可以通过调用 AsQueryable 方法来生成一个普通可枚举集合的iquerable 包装器。

 

我们先用 LINQ to SQL 说明 interpreted query 架构,因为 LINQ to SQL 不需要 Entity Data Model。 但是,我们编写的查询在 Entity Framework(以及许多第三方产品)上同样有效。

IQuerizable 是IEnumerable的扩展,具有构造表达式树的其他方法。大多数时候你可以忽略这些方法的细节;它们被框架间接地调用。

假设我们在SQL Server中创建一个简单的customer表,并使用以下SQL脚本用几个名称填充它:

create table Customer
(
​    ID int not null primary key,
​    Name varchar(30)
)

insert Customer values (1, 'Tom')
insert Customer values (2, 'Dick')
insert Customer values (3, 'Harry')
insert Customer values (4, 'Mary')
insert Customer values (5, 'Jay')

有了这个表,我们可以用c#编写一个解释LINQ查询来检索客户名中包含字母“a”的客户,如下所示:

using System;
using System.Linq;
using System.Data.Linq; // in System.Data.Linq.dll
using System.Data.Linq.Mapping;​
​
[Table] public class Customer
{
     [Column(IsPrimaryKey=true)] public int ID;
     [Column] public string Name;
}
​
class Test
{
     static void Main()
     {
         DataContext dataContext = new DataContext ("connection string");
         Table customers = dataContext.GetTable ();
         IQueryable<string> query = from c in customers
             where c.Name.Contains ("a")
             orderby c.Name.Length
         select c.Name.ToUpper();        
​
         foreach (string name in query) Console.WriteLine (name);
     }
}

上面的LINQ to SQL 转为 SQL会像下面这样:

SELECT UPPER([t0].[Name]) AS [value]
FROM [Customer] AS [t0]
WHERE [t0].[Name] LIKE @p0
ORDER BY LEN([t0].[Name])
​
//with the following end result:
JAY
MARY
HARRY

How Interpreted Queries Work

1、首先,编译器将 query 语法转为 fluent语法,这和本地查询完全一样。

IQueryable<string> query = customers.Where (n => n.Name.Contains ("a"))
                                     .OrderBy (n => n.Name.Length)
                                     .Select (n => n.Name.ToUpper());

2、接下来编译器解析查询操作符方法,这里解释查询就和本地查询不一样了, 解释查询解析查询操作在 Queryable 类,而不是Enumerable 类中的操作符。

要了解原因,我们需要查看customers变量,这是构建整个查询的源。 customer类型为Table,它实现了iquerizable (IEnumerable的一个子类型)。 这意味着编译器有一个选择在解析 where 操作符的时候 :它可以调用Enumerable中的扩展方法,也可以调用query中的扩展方法:

public static IQueryable Where (this IQueryable source, 
                                                  Expression bool>> predicate)

编译器选择 Queryable.where 因为它的签名是更具体的匹配。

Querable.where 接收一个谓词,这个谓词被封装在一个 Expression 类型中。这指导编译器转换提供的lambda 表达式为一个表达式树,而不是编译为一个委托

一个表达式树是一个对象模型,这个模型基于在 System.Linq.Expressions 中的类型,而它可以在运行时被检查。这样 LINQ to SQL or EF 以后可以把它转换成SQL语句

因为 Queryable.Where 返回一个 IQueryable,所以Orderby 和 Select 操作符也一样。 返回结果的说明见Figure 8-9. 在这个 shaded box, 有一个表达式树描述整个查询,它可以在运行时遍历。

 LINQ - 解释查询_第1张图片

 

 

 

Execution

Interpreted queries 也遵循延时执行模式,就像本地查询一样。这意味着直到你枚举这个查询时,这个SQL 语句才会生成。还有要注意,在同一个query 上枚举两次会导致在数据库上执行两次查询。

解释查询 和本地查询不同的地方在哪呢?不同之处在于它们的执行方式。

  • 当您枚举 Interpreted queries时, 最外层的序列运行一个程序,该程序贯穿整个表达式树,将其作为一个单元处理。

我们前面说 LINQ query 像是一个生产线。但是,当您枚举一个iquerable输送带时,它不会像本地查询那样启动整个生产线。相反,它只是启动了 IQueryable 传送带,并使用了一个特殊的枚举器,这个枚举器称之为 生产经理。 经理审查整个生产线,其中不包括已编译的代码。 经理检查整个生产线,它不是由编译的代码组成的,而是由一些傻瓜(方法调用表达式)组成的,并将指令粘贴到他们的前额(表达式树)。然后经理遍历所有表达式,在本例中,将它们转录到一张纸上(一条SQL语句),然后执行该语句,将结果反馈给使用者。 只有一条皮带转动,生产线的其余部分是一个由空壳组成的网络,存在只是为了描述必须要做什么。

在本地查询中你可以写一个自己的 query 方法,但是在 remote query 中就不可能了。因为枚举器不会识别你自己写的 query 方法。

可查询的标准方法集定义了用于查询任何远程集合的标准词汇表。

 

Combining Interpreted and Local Queries

一个查询可以既有本地查询也有解释查询。典型模式是本地查询在外部,解释查询在内部。换句话说,这个解释查询 的结果可作为 本地查询的输入。

假设我们要写一个 custom 表达式方法 来pair up 字符串在一个集合中。

public static IEnumerable<string> Pair (this IEnumerable<string> source)
{
     string firstHalf = null;
     foreach (string element in source)
     if (firstHalf == null)
         firstHalf = element;
     else
     {
         yield return firstHalf + ", " + element;
         firstHalf = null;
     }
}

上面定义了一个本地查询方法,可以在一个 解释查询中内使用这个方法。

DataContext dataContext = new DataContext ("connection string");
Table customers = dataContext.GetTable ();
​
IEnumerable<string> q = customers
     .Select (c => c.Name.ToUpper())
     .OrderBy (n => n)
     .Pair() // Local from this point on.
     .Select ((n, i) => "Pair " + i.ToString() + " = " + n);
​
foreach (string element in q) Console.WriteLine (element);
//输出结果为
Pair 0 = HARRY, MARY
Pair 1 = TOM, DICK

因为 customers 是一个实现了 IQueryable接口的类型,所以 Select 操作符要使用 Querable.Select ,而它又返回的是 IQueryable接口的类型,所以后面的 OrderBy 也是 Querable.OrderBy。但是接下来的操作符 Pair 是自己写的,它实现的是 IEnumerable, 因此,它解析为我们的本地Pair 方法——将interpreted query 包装在本地查询中。Pair还返回IEnumerable,因此后面的Select 也会解析为另一个本地操作符。

剩下的工作在本地完成。实际上,我们最终得到一个本地查询(外部),它的源是一个解释查询(内部)。

AsEnumerable

Enumerable.AsEnumerable 是所有 query 操作符中最简单的一个。下面是它的完整定义。

public static IEnumerable AsEnumerable(this IEnumerable source)
{
     return source;
}

它的目的是将IQuerable 序列转换为IEnumerable, 强制后续查询操作符绑定到Enumerable操作符,而不是Querable 操作符。这将导致查询的其余部分在本地执行。

假设我们有一个 MedicalArticles 表在 SQL Server 中,想用 LINQ to SQL or EF 来检索所有摘要少于100字的流感文章, 对于后一个谓词,我们需要一个正则表达式:

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");
var query = dataContext.MedicalArticles
     .Where (article => article.Topic == "influenza" &&
             wordCounter.Matches (article.Abstract).Count < 100);

问题是 SQL Server 不支持正则表达式,所以要用两步来解决这个问题,首先用 LINQ to SQL query 检索所有的流感文章,然后在本地筛选摘要少于 100 字的文章。

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");
IEnumerable sqlQuery = dataContext.MedicalArticles
     .Where (article => article.Topic == "influenza");
IEnumerable localQuery = sqlQuery
     .Where (article => wordCounter.Matches (article.Abstract).Count < 100);

有了 AsEnumerable , 我们可以用一个查询步骤完成上面的操作。

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");
var query = dataContext.MedicalArticles
     .Where (article => article.Topic == "influenza")
     .AsEnumerable()
     .Where (article => wordCounter.Matches (article.Abstract).Count < 100);

另一种调用 AsEnumerable 方法是 调用 ToArray 或 ToList。 AsEnumerable的优点是它不强制立即执行查询,也不创建任何存储结构。

将查询处理从数据库服务器转移到客户机可能会降低性能,特别是如果这意味着检索更多的行。

一个更高效的方法解决我们上面的示例,就是用 SQL CLR 集成 来公开一个在数据库上的函数,这个函数实现正则表达式。

小结:

解释查询与 本地查询的不同之处在于,它是对 IQuerable接口实现的序列进行操作,操作符也不是在原来的System.Linq.Enumerable类中实现的了,而是在System.Linq.Querable 中实现。这些操作符也不是接收一个委托,而是一个Expression 包装的委托。编译器会将这些操作符构成的 ”生产线 “ 转为表达式树,最终作为 SQL 传给数据库执行查询操作。

 

LINQ - 解释查询_第2张图片

 

你可能感兴趣的:(LINQ - 解释查询)