LINQ:构建 IQueryable Provider - 第 I 部分:可重用 IQueryable 基类

英文原文是Matt Warren发表在MSDN Blogs的系列文章之一,英文渣渣,翻译不供参考,请直接看原文

我要说的第一件事是,在Beta 2版本里面,IQueryable不再只是一个接口,它被分成了两个:IQueryableIQueryProvider。在实现这两个接口之前,我们先过一遍它们的内容。
使用Visual Studio的“go to definition”功能,你可以看到下面的代码

	public interface IQueryable : IEnumerable {       
	    Type ElementType { get; }
	    Expression Expression { get; }
	    IQueryProvider Provider { get; }
	}
	public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable {
	}

当然,IQueryable现在已经没什么好看的,有趣的内容都被放到了新接口IQueryProvider那里。如你所见,IQueryable只有三个只读的属性。第一个属性返回元素的类型(或者IQueryable<T>里面的T)。注意,所有实现IQueryable的类都必须同时实现IQueryable<T>,反之亦然。泛型的IQueryable<T>是在方法签名里面使用得最频繁的。非泛型的IQueryable的存在主要是为了提供一个弱类型的入口,该入口主要应用在动态构建query的场景之中。

第二个属性返回这个IQueryable对象对应的Expression,这正是IQueryable的精髓所在。在IQueryable封装之下的真正的“查询”是一个表达式树,它将query对象表示为一个由LINQ查询方法/操作符组成的树形结构,这是构建一个LINQ提供程序必须理解的原理。仔细看你就会发现,整个IQueryable的结构体系(包括LING标准查询操作符的System.Linq.Queryable版本)只是自动为你创建了表达式树。当你使用Queryable.Where方法来过滤IQueryable中的数据的时候,它只是简单地创建了一个新的IQueryable对象,并在原有的表达式树顶上创建一个MethodCallExpression类型的节点,该节点表示一次Queryable.Where方法的调用。不信?你自己试试看就知道了。

现在就只剩最后一个属性,这个属性返回新接口IQueryProvider的实例。我们把所有构造IQueryable实例和执行查询的方法都分离到了这个新接口中,这样能更加清晰地表示出查询提供程序的概念。

	public interface IQueryProvider {
	    IQueryable CreateQuery(Expression expression);
	    IQueryable<TElement> CreateQuery<TElement>(Expression expression);
	    object Execute(Expression expression);
	    TResult Execute<TResult>(Expression expression);
	}

看到这个IQueryProvider接口,你可能会疑惑为什么有这么多方法。实际上这里只有两个操作,CreateQueryExecute,只不过每个操作都有一个泛型的版本和一个非泛型的版本。当你直接在代码里面写查询的时候,一般都是调用泛型的版本。使用泛型的版本可以避免使用反射创建实例,因此性能更佳。

正如其名,CreateQuery方法的作用是根据指定的表达式树创建一个新的IQueryable对象。当这个方法被调用时,你的提供程序应该返回一个IQueryable对象,这个对象被枚举的时候会调用你的提供程序来处理这个指定的表达式。Queryable的标准查询操作符就是调用这个方法来创建与你的提供程序保持关联的IQueryable对象。注意,调用者可能会传给你的这个API一个任意的表达式树,对你的提供程序而言,传入的表达式树甚至可能是非法的,但是可以保证的是它一定会符合IQueryable对象的类型要求。IQueryable对象包含了一个表达式,这个表达式是一个代码的片段,当它转换为真正的代码并且执行的时候就会重新构造一个等价的IQueryable对象。

Execute方法是你的提供程序真正执行查询表达式的入口。应提供一个明确的Execute方法而不要仅仅依赖于IEnumerable.GetEnumerator(),以支持那些不必返回一个序列的查询。比如,这个查询“myquery.Count()”返回一个整数,该查询的表达式树是对返回整数的Count方法的调用。Queryable.Count方法(以及其他类似的聚合方法)就是调用Execute来“立即”执行查询。

讲到这里,是不是看起来就没那么难了?你自己也可以很轻松地实现所有的方法对吧?但是何必这么麻烦呢,我在下面就会给出代码。当然Execute方法除外,这个我会在以后的文章中给出。

让我们先从IQueryable开始。因为这个接口已经被划分成了两个,所以现在可以只用实现一次IQueryable,然后把它用在任意一个IQueryProvider中。下面给出一个Query<T>类,它实现了IQueryable<T>以及其他一系列的接口。

	public class Query<T> : IQueryable<T>, IQueryable, IEnumerable<T>, IEnumerable, IOrderedQueryable<T>, IOrderedQueryable {
	    QueryProvider provider;
	    Expression expression;
	 
	    public Query(QueryProvider provider) {
	        if (provider == null) {
	            throw new ArgumentNullException("provider");
	        }
	        this.provider = provider;
	        this.expression = Expression.Constant(this);
	    }
	 
	    public Query(QueryProvider provider, Expression expression) {
	        if (provider == null) {
	            throw new ArgumentNullException("provider");
	        }
	        if (expression == null) {
	            throw new ArgumentNullException("expression");
	        }
	        if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type)) {
	            throw new ArgumentOutOfRangeException("expression");
	        }
	        this.provider = provider;
	        this.expression = expression;
	    }
	 
	    Expression IQueryable.Expression {
	        get { return this.expression; }
	    }
	 
	    Type IQueryable.ElementType {
	        get { return typeof(T); }
	    }
	 
	    IQueryProvider IQueryable.Provider {
	        get { return this.provider; }
	    }
	 
	    public IEnumerator<T> GetEnumerator() {
	        return ((IEnumerable<T>)this.provider.Execute(this.expression)).GetEnumerator();
	    }
	 
	    IEnumerator IEnumerable.GetEnumerator() {
	        return ((IEnumerable)this.provider.Execute(this.expression)).GetEnumerator();
	    }
	 
	    public override string ToString() {
	        return this.provider.GetQueryText(this.expression);
	    }
	}

你看,IQueryable的实现十分简单。这个小对象所做的事情仅仅是保持一颗表达式树和一个查询提供者的实例,而查询提供者才是真正有趣的地方。

好了,下面把Query<T>类中引用到的QueryProvider给出,它是一个抽象类。一个真正的提供程序只需继承这个类,实现里面的Execute抽象方法。

	public abstract class QueryProvider : IQueryProvider {
	    protected QueryProvider() {
	    }
	 
	    IQueryable<S> IQueryProvider.CreateQuery<S>(Expression expression) {
	        return new Query<S>(this, expression);
	    }
	 
	    IQueryable IQueryProvider.CreateQuery(Expression expression) {
	        Type elementType = TypeSystem.GetElementType(expression.Type);
	        try {
	            return (IQueryable)Activator.CreateInstance(typeof(Query<>).MakeGenericType(elementType), new object[] { this, expression });
	        }
	        catch (TargetInvocationException tie) {
	            throw tie.InnerException;
	        }
	    }
	 
	    S IQueryProvider.Execute<S>(Expression expression) {
	        return (S)this.Execute(expression);
	    }
	 
	    object IQueryProvider.Execute(Expression expression) {
	        return this.Execute(expression);
	    }
	 
	    public abstract string GetQueryText(Expression expression);
	    public abstract object Execute(Expression expression);
	}

这个抽象类实现了IQueryProvider接口。两个CreateQuery方法负责创建Query<T>的实例,两个Execute方法将执行操作交给了尚未实现的Execute抽象方法。

我认为你可以把这个当成构建LINQ IQueryable提供程序的样板代码。真正的执行操作放在Execute方法中,在这里,你的提供程序可以通过检查表达式树来理解查询的具体含义,而这就是我接下来要讲的内容。

更新:
我好像忘了定义在代码里面用到的helper类,下面给出。

	internal static class TypeSystem {
	    internal static Type GetElementType(Type seqType) {
	        Type ienum = FindIEnumerable(seqType);
	        if (ienum == null) return seqType;
	        return ienum.GetGenericArguments()[0];
	    }
	    private static Type FindIEnumerable(Type seqType) {
	        if (seqType == null || seqType == typeof(string))
	            return null;
	        if (seqType.IsArray)
	            return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
	        if (seqType.IsGenericType) {
	            foreach (Type arg in seqType.GetGenericArguments()) {
	                Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
	                if (ienum.IsAssignableFrom(seqType)) {
	                    return ienum;
	                }
	            }
	        }
	        Type[] ifaces = seqType.GetInterfaces();
	        if (ifaces != null && ifaces.Length > 0) {
	            foreach (Type iface in ifaces) {
	                Type ienum = FindIEnumerable(iface);
	                if (ienum != null) return ienum;
	            }
	        }
	        if (seqType.BaseType != null && seqType.BaseType != typeof(object)) {
	            return FindIEnumerable(seqType.BaseType);
	        }
	        return null;
	    }
	}

好吧,我知道这个helper类的代码比其他地方的都多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值