【翻译】创建IQUERYABLE提供器系列文章

翻译自 http://blogs.msdn.com/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx,持续更新

可重用的IQueryable基类

很久就想开始一个使用IQueryable介绍创建LINQ提供器的系列文章了。一直有人通过微软内部邮件或论坛提问问我相关的建议。当然,我也一直回答他们说,我正在做一个简单示例,很快就会让你们知道一切。然而,我希望一步一步来深入并解释一切,而不是一下子给你们一个完整的示例,让你们自己去探索。

首先,我应该指出的是在Beta2IQueryable有改变。它不再只一个接口,而是分成了两个:IQueryableIQueryProvider。在实现它们之前,让我们先来看看。

如果你使用Visual Studio的“转到定义”,会看到如下代码:

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只有三个只读接口。第一个给出元素类型(或IQueryable<T>中的T)。需要知道,所有实现IQueryable的类必须为某个T实现IQueryable<T>,反之亦然。泛型IQueryable<T>在方法签名等中非常常用。开始的非泛型在动态查询创建的应用中给出了弱类型的入口点。

第二个属性给出了查询对应的表达式。这是IQueryable的本质。IQueryable的幕后英雄其实是表示LINQ查询运算符或方法调用的表达式。提供器必须包含这部分才能进行有用的操作。如果我们继续深入的话会看到整个IQueryable构架(包括LINQ标准查询运算符的System.Linq.Queryable版本)只是一种自动构建表达式树的机制。当我们使用Queryable.Where方法来为IQueryable应用过滤的时候,它只是为我们创建了一个新的IQueryable,在其树的顶部增加了一个表示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方法是提供器的入口点,用于实际执行查询表达式。例如,查询“myquery.Count()”返回单个整数。查询的表达式树是对一个返回整数的Count方法的方法调用。Queryable.Count(以及其它一些相似的聚合)使用这个方法来立即执行查询。

There, that doesn’t seem to frightening does it? You could implement all those methods easily, right? Sure you could, but why bother. I’ll do it for you. Well all except for the execute method. I’ll show you how to do that in a later post.

看起来不是佷吓人吧。我们是不是可以轻松实现这些方法?是的,但是为什么要这么麻烦呢。我会为你实现这些。当然,除了Execute方法之外。我会在之后的文章中介绍如何实现。

首先,让我们从IQuerayble开始。由于这个接口被分成了两个,我们就可以只实现IQueryable部分一次,然后任何提供器都可以进行重用。我会实现一个叫做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);

}

在我的QueryProvider基类中实现了IQueryProvider接口。CreateQuery方法创建了Query<T>的新实例并且Execute方法把执行转交给未实现的Execute方法。

我觉得你可以把这段代码看作是开始创建LINQ IQueryable提供器的模板。真的操作在Execute方法内部执行。在那个时候,提供器通过分析表达式树来理解查询。下回会继续分解。

更新:

似乎我忘记定义了类实现中使用的帮助类,下面是代码:

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;

}

}


Where以及可重用的表达式访问器

既然我已经定义了叫做Query<T>QueryProvider的可重用版本的IQueryableIQueryProvider。我将会创建有实际操作的提供器。我之前说过,查询表达式真正做的只是执行一小段定义为表达式树而不是实际IL的“代码”。当然,从传统意义上说不一定要真正执行。例如,LINQ to SQL把查询表达式翻译为SQL并且发送给服务器来执行。

我下面的示例将要做和LINQ to SQL差不多的事情,它翻译并通过ADO提供器执行查询。然而,我要声明这个示例不是完整的。我只会处理Where运算符,并且不会尝试进行任何比允许谓词包含字段引用以及一些简单运算符更复杂的事情。我会在将来扩展这个提供器,但是现在只是用于演示。请不要剪切粘帖代码或期望它用于产品。

这个提供器将会做两个事情:1、翻译查询为SQL命名文本;2、翻译命令的执行结果为对象。

QueryTranslator

QueryTranslator访问查询表达式树中的每一个节点,并且使用StringBuilder把提供的运算符翻译为文本。为了简单期间,我们假设有一个叫做ExpressionVisitor的类,它定义了表达式节点的访问者模式。

internal class QueryTranslator : ExpressionVisitor {

StringBuilder sb;

internal QueryTranslator() {

}

internal string Translate(Expression expression) {

this.sb = new StringBuilder();

this.Visit(expression);

return this.sb.ToString();

}

private static Expression StripQuotes(Expression e) {

while (e.NodeType == ExpressionType.Quote) {

e = ((UnaryExpression)e).Operand;

}

return e;

}

protected override Expression VisitMethodCall(MethodCallExpression m) {

if

你可能感兴趣的:(sql,编程,应用服务器,LINQ)