LINQ - Custom LINQ Provider (IQueryable, IQueryProvider)

[ 2007-10-20 00:47:10 | 作者: yuhen ]
字号: | |
在博客堂看到 Kevin Halverson 的文章 《如何实现IQueryable (Kevin Halverson)》(VBCTI 翻译),意识到自己还差一堂功课没补上。

在前面分析 LINQ to SQL 的文章中,我们已经明白要实现一个自定义的 LINQ provider,需要实现两个接口 —— IQueryable<T> 和 IQueryProvider。其中 IQueryable<T> 是主要的外部操作接口,被 LINQ 扩展方法(System.Linq.Queryable)调用,用于保持源数据和查询状态;IQueryProvider 则是整个算法核心,分解表达式(Expression), "计算" 并 "筛选" 出我们期望的结果集(IEnumerable<T>)。

看看两个接口在 MSDN 中的说明。

IQueryable<T>
/// <summary>
/// Provides functionality to evaluate queries against a specific data source wherein 
/// the type of the data is known.
/// </summary>
public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable
{
}

/// <summary>
/// Provides functionality to evaluate queries against a specific data source wherein 
/// the type of the data is not specified.
/// </summary>
public interface IQueryable : IEnumerable
{
    /// <summary>
    /// Gets the type of the element(s) that are returned when the expression tree associated 
    /// with this instance of IQueryable is executed.
    /// </summary>
    Type ElementType { get; }

    /// <summary>
    /// Gets the expression tree that is associated with the instance of IQueryable.
    /// </summary>
    Expression Expression { get; }
    
    /// <summary>
    /// Gets the query provider that is associated with this data source.
    /// </summary>    
    IQueryProvider Provider { get; }
}

IQueryProvider
/// <summary>
/// Defines methods to create and execute queries that are described by an IQueryable object.
/// </summary>
public interface IQueryProvider
{
    /// <summary>
    /// Constructs an IQueryable object that can evaluate the query represented by 
    /// a specified expression tree.
    /// </summary>    
    IQueryable CreateQuery(Expression expression);

    /// <summary>
    /// Constructs an IQueryable<(Of <T>)> object that can evaluate the query represented by 
    /// a specified expression tree.
    /// </summary>    
    IQueryable<TElement> CreateQuery<TElement>(Expression expression);

    /// <summary>
    /// Executes the query represented by a specified expression tree.
    /// </summary>    
    object Execute(Expression expression);

    /// <summary>
    /// Executes the strongly-typed query represented by a specified expression tree.
    /// </summary>    
    TResult Execute<TResult>(Expression expression);
}

首先我们创建一个实现了 IQueryable<T> 的对象,这个对象对象内部包含了一个 T[] Data 数据源。
public class MyQuery<T> : IQueryable<T>
{
    internal Expression expression;
    private MyQueryProvider<T> provider;
    public T[] Data { get; set; }

    // ------------- IEnumerator Member ---------------------
    
    public IEnumerator<T> GetEnumerator()
    {
        return provider.Execute<IEnumerator<T>>(this.expression);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (this as IEnumerable<T>).GetEnumerator();
    }

    // ------------- IQueryable<T> Member ---------------------

    /// <summary>
    /// 获取元素类型
    /// </summary>
    public Type ElementType
    {
        get { return typeof(T); }
    }

    /// <summary>
    /// 获取数据源(IQueryable)常量表达式
    /// </summary>
    public Expression Expression
    {
        get { return Expression.Constant(this); }
    }

    /// <summary>
    /// 获取 Provider 对象
    /// </summary>
    public IQueryProvider Provider
    {
        get 
        {
            if (provider == null)
                provider = new MyQueryProvider<T>(this); 

            return provider;
        }
    }
}

接下来,我们编写 MyQueryProvider(LINQ Provider) 类型。为了和 MyQueryable<T> 交互,我们通过构造方法传递了对象引用。
public class MyQueryProvider<T> : IQueryProvider
{
    private MyQuery<T> query;

    public MyQueryProvider(MyQuery<T> query)
    {
        this.query = query;
    }

    // ------------- IQueryProvider Member ---------------------

    /// <remark>
    /// 构造一个可用来执行 "表达式" 计算的 IQueryable 对象。
    /// </remark>
    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        query.expression = expression;
        return (IQueryable<TElement>)query;
    }

    /// <remark>
    /// 构造一个可用来执行 "表达式" 计算的 IQueryable 对象。
    /// </remark>
    public IQueryable CreateQuery(Expression expression)
    {
        return CreateQuery<T>(expression);
    }

    /// <remark>
    /// 执行表达式 "计算"
    /// </remark>
    public TResult Execute<TResult>(Expression expression)
    {
        // 以下代码并不完整,我只是实现了 "i > 3" 这样的表达式…… 
        
        var exp = expression as MethodCallExpression;
        var data = ((exp.Arguments[0] as ConstantExpression).Value as MyQuery<T>).Data;
        var func = (exp.Arguments[1] as UnaryExpression).Operand as Expression<System.Func<T, bool>>;
        var lambda = Expression.Lambda<Func<T, bool>>(func.Body, func.Parameters[0]);

        var r = data.Where(lambda.Compile());
        return (TResult)r.GetEnumerator();
    }

    /// <remark>
    /// 执行表达式 "计算"
    /// </remark>
    public object Execute(Expression expression)
    {
        return Execute<T>(expression);
    }
}

最后,我们开始测试一个作坊式的 "发动机",看看效果。
class Program
{
    static void Main(string[] args)
    {
        var o = new MyQuery<int> { Data = new[] { 1, 2, 3, 4, 5 } };
        var q = from i in o where i > 3 select i;
        foreach (var item in q)
        {
            Console.WriteLine(item);
        }
    }
}

输出:
4
5

还行,如果我们能补全表达式计算代码,应该还是个不错的东西。

依照惯例,我们会进行反编译,并试图分析一下该演示的执行流程。

(1) 调用代码很好理解,首先构造表达式树,然后调用扩展方法 Queryable.Where()。
static void Main(string[] args)
{
    MyQuery<int> o = new MyQuery<int>();
    o.Data = new int[] { 1, 2, 3, 4, 5 };

    ParameterExpression e = Expression.Parameter(typeof(int), "i");
    BinaryExpression bin = Expression.GreaterThan(e, Expression.Constant(3, typeof(int)));
    Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(bin, 
        new ParameterExpression[] { e });

    IQueryable<int> q = Queryable.Where<int>(o, lambda);

    foreach (int item in q)
    {
        Console.WriteLine(item);
    }
}

(2) 在 Queryable.Where() 中 MyQuery<T>.Provider 和 MyQuery<T>.Expression 被调用,MyQuery<T>.Expression 作为参数传递给 MyQueryProvider.CreateQuery<TElement>() 。

MyQueryProvider.CreateQuery<TElement>() 方法返回一个数据源,通常我们还会将 expression 回传给 Queryable<T> 对象,以便其在稍候调用 MyQueryProvider.Execute<TResult>() 时作为参数。至此,我们所持有的依然是原本那个对象 o,只不过这里面多了个表达式。
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        // ...
        
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), 
                new Expression[] { source.Expression, Expression.Quote(predicate) }
            )
        );
    }
}

(3) 当开始执行 Main() "foreach (int item in q)" 代码时,将会触发 LINQ 延时执行机制,MyQuery<T>.GetEnumerator() 方法被调用。在该方法内部我们将上一步存储的表达式传递给了 MyQueryProvider.Execute<TResult>(),同时指定了返回集合的类型。
public IEnumerator<T> GetEnumerator()
{
    return provider.Execute<IEnumerator<T>>(this.expression);
}

(4) 在 MyQueryProvider.Execute<TResult>() 中,我们通过分解表达式树来创建相应的委托,最终返回经过筛选的结果。
public TResult Execute<TResult>(Expression expression)
{
    // 以下代码并不完整,我只是实现了 "i > 3" 这样的表达式……
        
    var exp = expression as MethodCallExpression;
    var data = ((exp.Arguments[0] as ConstantExpression).Value as MyQuery<T>).Data;
    var func = (exp.Arguments[1] as UnaryExpression).Operand as Expression<System.Func<T, bool>>;
    var lambda = Expression.Lambda<Func<T, bool>>(func.Body, func.Parameters[0]);

    var r = data.Where(lambda.Compile());
    return (TResult)r.GetEnumerator();
}

uploads/200710/20_004755_1.gif


----------------

参考文章:

《动态创建 Lambda 表达式》
《LINQ to SQL 执行流程不完整分析 (Update)》
《LINQ - 延迟执行机制分析》
《LINQ: Building an IQueryable Provider》
《Writing custom LINQ provider》《Custom LINQ provider (Continued..)》
[最后修改由 yuhen, 于 2007-10-21 20:30:06]
评论Feed 评论Feed: http://www.rainsts.net/feed.asp?q=comment&id=613

这篇日志没有评论。

发表评论
表情图标
[smile] [confused] [cool] [cry]
[eek] [angry] [wink] [sweat]
[lol] [stun] [razz] [redface]
[rolleyes] [sad] [yes] [no]
[heart] [star] [music] [idea]
UBB代码
转换链接
表情图标
悄悄话
用户名:   密码:  
验证码 * 请输入验证码