打造自己的LINQ Provider(中):IQueryable和IQueryProvider
概述
在.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 Provider(上):Expression Tree揭秘》一文的最后,我说到了这样一句话:需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,带着这个问题,我们先来看下面这段代码,查询的结果query为IEnumerable<String>类型:
static void Main(string[] args) { List<String> myList = new List<String>() { "a", "ab", "cd", "bd" }; IEnumerable<String> query = from s in myList where s.StartsWith("a") select s; foreach (String s in query) { Console.WriteLine(s); } Console.Read(); }
这里将返回两条结果,如下图所示:
这里就有一个问题,为什么在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这样的属性,如下图所示:
至于LINQ to Objects中所有的标准查询操作符都是通过扩展方法来实现的,它们在抽象类Enumerable中定义,如其中的Where扩展方法如下代码所示:
public static class Enumerable { public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return WhereIterator<TSource>(source, predicate); } public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return WhereIterator<TSource>(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<String>() { "a", "ab", "cd", "bd" }.AsQueryable<String>(); IQueryable<String> query = from s in myList where s.StartsWith("a") select s; foreach (String s in query) { Console.WriteLine(s); } Console.Read(); }
运行这段代码,虽然它的输出结果与上面的示例完全相同,但它们查询的机制却完全不同:
IQueryable<T>接口
在.NET中,IQueryable<T>继承于IEnumerable<T>和IQueryable接口,如下图所示:
这里有两个很重要的属性Expression和Provider,分别表示获取与IQueryable 的实例关联的表达式目录树和获取与此数据源关联的查询提供程序,我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,而最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言,这个数据源可能是数据库,XML文件或者是WebService等。该接口非常重要,在我们自定义LINQ Provider中必须要实现这个接口。同样对于IQueryable的标准查询操作都是由Queryable中的扩展方法来实现的,如下代码所示:
public static class Queryable { public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return source.Provider.CreateQuery<TSource>( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) })); } public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return source.Provider.CreateQuery<TSource>( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) })); } }
最后还有一点,如果我们定义的查询需要支持Orderby等操作,还必须实现IOrderedQueryable<T> 接口,它继承自IQueryable<T>,如下图所示:
IQueryProvider接口
在认识了IQueryable接口之后,我们再来看看在自定义LINQ Provider中另一个非常重要的接口IQueryProvider。它的定义如下图所示:
看到这里两组方法的参数,其实大家已经可以知道,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<T> : IEnumerable<T> where T : class { public IEnumerator<T> GetEnumerator() { return null; } IEnumerator IEnumerable.GetEnumerator() { return null; } // 其它成员 }
第二种扩展LINQ的方式当然就是自定义LINQ Provider了,我们需要实现IQueryable<T>和IQueryProvider两个接口,下面先给出一段简单的示意代码,在下一篇中我们将完整的来实现一个LINQ Provider。如下代码所示:
public class QueryableData<TData> : IQueryable<TData> { 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<TData>).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<TData> GetEnumerator() { return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return (Provider.Execute<IEnumerable>(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<TResult> CreateQuery<TResult>(Expression expression) { return new QueryableData<TResult>(this, expression); } public object Execute(Expression expression) { // ...... } public TResult Execute<TResult>(Expression expression) { // ...... } }
上面这两个接口都没有完成,这里只是示意性的代码,如果实现了这两个接口,我们就可以像下面这样使用了(当然这样的使用是没有意义的,这里只是为了演示):
static void Main(string[] args) { QueryableData<String> mydata = new QueryableData<String> { "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方法,此时查询才会被真正执行,如下图所示:
总结
本文介绍了在自定义LINQ Provider中两个最重要的接口IQueryable和IQueryProvider,希望对大家有所帮助,下一篇我我们将开发一个完整的自定义LINQ Provider。
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com