LINQ - 解释查询

4 、Interpreted Queries

LINQ提供了两种并行架构:

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

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

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

解释查询是描述性的

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

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

There are two IQueryable<T> implementations in the .NET Framework:

  • LINQ to SQL

  • Entity Framework (EF)

    扫描二维码关注公众号,回复: 7334508 查看本文章

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

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

 

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

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

假设我们在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<Customer> customers = dataContext.GetTable <Customer>();
         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<T>,它实现了iquerizable <T> (IEnumerable<T>的一个子类型)。 这意味着编译器有一个选择在解析 where 操作符的时候 :它可以调用Enumerable中的扩展方法,也可以调用query中的扩展方法:

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

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

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

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

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

 

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<Customer> customers = dataContext.GetTable <Customer>();
​
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<T>接口的类型,所以 Select 操作符要使用 Querable.Select ,而它又返回的是 IQueryable<T>接口的类型,所以后面的 OrderBy 也是 Querable.OrderBy。但是接下来的操作符 Pair 是自己写的,它实现的是 IEnumerable<T>, 因此,它解析为我们的本地Pair 方法——将interpreted query 包装在本地查询中。Pair还返回IEnumerable,因此后面的Select 也会解析为另一个本地操作符。

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

AsEnumerable

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

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

它的目的是将IQuerable <T>序列转换为IEnumerable<T>, 强制后续查询操作符绑定到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<MedicalArticle> sqlQuery = dataContext.MedicalArticles
     .Where (article => article.Topic == "influenza");
IEnumerable<MedicalArticle> 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 传给数据库执行查询操作。

 

猜你喜欢

转载自www.cnblogs.com/mingjie-c/p/11568504.html
今日推荐