博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
打造自己的LINQ Provider(中):IQueryable和IQueryProvider (转 李会军)
阅读量:5200 次
发布时间:2019-06-13

本文共 7875 字,大约阅读时间需要 26 分钟。

概述

在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Provider。

本文为打造自己的LINQ Provider系列文章第二篇,主要详细介绍自定义LINQ Provider中两个最重要的接口IQueryable和IQueryProvider。 

IEnumerable<T>接口

在上一篇《》一文的最后,我说到了这样一句话:需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,带着这个问题,我们先来看下面这段代码,查询的结果query为IEnumerable<String>类型:

static void Main(string[] args){    List
myList = new List
() { "a", "ab", "cd", "bd" }; IEnumerable
query = from s in myList where s.StartsWith("a") select s; foreach (String s in query) { Console.WriteLine(s); } Console.Read();}

这里将返回两条结果,如下图所示:

TerryLee_0170

这里就有一个问题,为什么在LINQ to Objects中返回的是IEnumerable<T>类型的数据而不是IQueryable<T>呢?答案就在本文的开始,在LINQ to Objects中查询表达式或者Lambda表达式并不翻译为表达式目录树,因为LINQ to Objects查询的都是实现了IEnmerable<T>接口的数据,所以查询表达式或者Lambda表达式都可以直接转换为.NET代码来执行,无需再经过转换为表达式目录这一步,这也是LINQ to Objects比较特殊的地方,它不需要特定的LINQ Provider。我们可以看一下IEnumerable<T>接口的实现,它里面并没有Expression和Provider这样的属性,如下图所示:

TerryLee_0171

至于LINQ to Objects中所有的标准查询操作符都是通过扩展方法来实现的,它们在抽象类Enumerable中定义,如其中的Where扩展方法如下代码所示:

public static class Enumerable{    public static IEnumerable
Where
( this IEnumerable
source, Func
predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return WhereIterator
(source, predicate); } public static IEnumerable
Where
( this IEnumerable
source, Func
predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return WhereIterator
(source, predicate); }}

注意到这里方法的参数Func<TSource>系列委托,而非Expression<Func<TSource>>,在本文的后面,你将看到,IQueryable接口的数据,这些扩展方法的参数都是Expression<Func<TSource>>,关于它们的区别在我已经说过了。同样还有一点需要说明的是,在IEnumerable<T>中提供了一组扩展方法AsQueryable(),可以用来把一个IEnumerable<T>类型的数据转换为IQueryable<T>类型,如下代码所示:

static void Main(string[] args){    var myList = new List
() { "a", "ab", "cd", "bd" }.AsQueryable
(); IQueryable
query = from s in myList where s.StartsWith("a") select s; foreach (String s in query) { Console.WriteLine(s); } Console.Read();}

运行这段代码,虽然它的输出结果与上面的示例完全相同,但它们查询的机制却完全不同:

TerryLee_0170

IQueryable<T>接口

在.NET中,IQueryable<T>继承于IEnumerable<T>和IQueryable接口,如下图所示:

TerryLee_0172

这里有两个很重要的属性Expression和Provider,分别表示获取与IQueryable 的实例关联的表达式目录树和获取与此数据源关联的查询提供程序,我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,而最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言,这个数据源可能是数据库,XML文件或者是WebService等。该接口非常重要,在我们自定义LINQ Provider中必须要实现这个接口。同样对于IQueryable的标准查询操作都是由Queryable中的扩展方法来实现的,如下代码所示:

public static class Queryable{    public static IQueryable
Where
(this IQueryable
source, Expression
> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return source.Provider.CreateQuery
( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) })); } public static IQueryable
Where
(this IQueryable
source, Expression
> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return source.Provider.CreateQuery
( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) })); }}

最后还有一点,如果我们定义的查询需要支持Orderby等操作,还必须实现IOrderedQueryable<T> 接口,它继承自IQueryable<T>,如下图所示:

TerryLee_0173

IQueryProvider接口

在认识了IQueryable接口之后,我们再来看看在自定义LINQ Provider中另一个非常重要的接口IQueryProvider。它的定义如下图所示:

TerryLee_0174

看到这里两组方法的参数,其实大家已经可以知道,Provider负责执行表达式目录树并返回结果。如果是LINQ to SQL的Provider,则它会负责把表达式目录树翻译为T-SQL语句并并传递给数据库服务器,并返回最后的执行的结果;如果是一个Web Service的Provider,则它会负责翻译表达式目录树并调用Web Service,最终返回结果。

这里四个方法其实就两个操作CreateQuery和Execute(分别有泛型和非泛型),CreateQuery方法用于构造一个 IQueryable<T> 对象,该对象可计算指定表达式目录树所表示的查询,返回的结果是一个可枚举的类型,;而Execute执行指定表达式目录树所表示的查询,返回的结果是一个单一值。自定义一个最简单的LINQ Provider,至少需要实现IQueryable<T>和IQueryProvider两个接口,在下篇文章中,你将看到一个综合的实例。

扩展LINQ的两种方式

通过前面的讲解,我们可以想到,对于LINQ的扩展有两种方式,一是借助于LINQ to Objects,如果我们所做的查询直接在.NET代码中执行,就可以实现IEnumerable<T>接口,而无须再去实现IQueryable并编写自定义的LINQ Provider,如.NET中内置的List<T>等。如我们可以编写一段简单自定义代码:

public class MyData
: IEnumerable
where T : class{ public IEnumerator
GetEnumerator() { return null; } IEnumerator IEnumerable.GetEnumerator() { return null; } // 其它成员}

第二种扩展LINQ的方式当然就是自定义LINQ Provider了,我们需要实现IQueryable<T>和IQueryProvider两个接口,下面先给出一段简单的示意代码,在下一篇中我们将完整的来实现一个LINQ Provider。如下代码所示:

public class QueryableData
: IQueryable
{ public QueryableData() { Provider = new TerryQueryProvider(); Expression = Expression.Constant(this); } public QueryableData(TerryQueryProvider provider, Expression expression) { if (provider == null) { throw new ArgumentNullException("provider"); } if (expression == null) { throw new ArgumentNullException("expression"); } if (!typeof(IQueryable
).IsAssignableFrom(expression.Type)) { throw new ArgumentOutOfRangeException("expression"); } Provider = provider; Expression = expression; } public IQueryProvider Provider { get; private set; } public Expression Expression { get; private set; } public Type ElementType { get { return typeof(TData); } } public IEnumerator
GetEnumerator() { return (Provider.Execute
>(Expression)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return (Provider.Execute
(Expression)).GetEnumerator(); }}public class TerryQueryProvider : IQueryProvider{ public IQueryable CreateQuery(Expression expression) { Type elementType = TypeSystem.GetElementType(expression.Type); try { return (IQueryable)Activator.CreateInstance( typeof(QueryableData<>).MakeGenericType(elementType), new object[] { this, expression }); } catch { throw new Exception(); } } public IQueryable
CreateQuery
(Expression expression) { return new QueryableData
(this, expression); } public object Execute(Expression expression) { // ...... } public TResult Execute
(Expression expression) { // ...... }}

上面这两个接口都没有完成,这里只是示意性的代码,如果实现了这两个接口,我们就可以像下面这样使用了(当然这样的使用是没有意义的,这里只是为了演示):

static void Main(string[] args){    QueryableData
mydata = new QueryableData
{ "TerryLee", "Cnblogs", "Dingxue" }; var result = from d in mydata select d; foreach (String item in result) { Console.WriteLine(item); }}

现在再来分析一下这个执行过程,首先是实例化QueryableData<String>,同时也会实例化TerryQueryProvider;当执行查询表达式的时候,会调用TerryQueryProvider中的CreateQuery方法,来构造表达式目录树,此时查询并不会被真正执行(即延迟加载),只有当我们调用GetEnumerator方法,上例中的foreach,此时会调用TerryQueryProvider中的Execute方法,此时查询才会被真正执行,如下图所示:

TerryLee_0178

总结

本文介绍了在自定义LINQ Provider中两个最重要的接口IQueryable和IQueryProvider,希望对大家有所帮助,下一篇我我们将开发一个完整的自定义LINQ Provider。

转载于:https://www.cnblogs.com/baiyu/archive/2011/07/05/2098470.html

你可能感兴趣的文章
Git的Patch功能
查看>>
魔术师发牌问题(循环链表的应用)【代码】
查看>>
mfc Edit控件属性
查看>>
你写程序再牛,也未必懂我写的文章!
查看>>
10.10 review
查看>>
Nodejs-非阻塞I/O&事件驱动
查看>>
ThreadPoolExecutor分析
查看>>
八张图读懂未来“互联网+”的六大趋势
查看>>
Linq使用Join/在Razor中两次反射取属性值
查看>>
[Linux]PHP-FPM与NGINX的两种通讯方式
查看>>
Java实现二分查找
查看>>
优秀员工一定要升职吗
查看>>
[LintCode] 462 Total Occurrence of Target
查看>>
K8s helm 创建自定义Chart
查看>>
a标签按钮样式
查看>>
JavaWeb--HttpServlet
查看>>
JavaEE——SpringMVC(2)--处理模型数据
查看>>
了解大数据
查看>>
springboot---redis缓存的使用
查看>>
架构图-模型
查看>>