• 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)

    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 (@"(w|[-'])+");
    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 (@"(w|[-'])+");
    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 (@"(w|[-'])+");
    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 传给数据库执行查询操作。

     

  • 相关阅读:
    线段树合并
    bzoj 3675 [Apio2014]序列分割
    模版总结【长期更新】
    动态规划的题目总结(长期更新)
    搜索(另类状态BFS):NOIP 华容道
    贪心(模拟费用流):NOIP2011 观光公交
    基础算法(二分,贪心):NOIP 2012 疫情控制
    模拟(堆):USACO Jan11 瓶颈
    搜索(DLX重复覆盖模板):HDU 2295 Radar
    数学:lucas定理的总结
  • 原文地址:https://www.cnblogs.com/mingjie-c/p/11568504.html
Copyright © 2020-2023  润新知