【问题标题】:Custom IQueryProvider that falls back on LinqToObjects依赖于 LinqToObjects 的自定义 IQueryProvider
【发布时间】:2012-08-02 20:15:38
【问题描述】:

我编写了一个自定义 IQueryProvider 类,该类接受一个表达式并针对 SQL 数据库对其进行分析(我知道我可以使用 Linq2Sql,但我需要进行一些修改和调整,不幸的是,这使得 Linq2Sql 不适合)。该类将识别并处理标记的属性(使用属性),但我希望能够将表达式传递给 LinqToObject 提供程序并允许它过滤之后的结果。

例如,假设我有以下 linq 表达式:

var parents=Context.Parents
    .Where(parent=>parent.Name.Contains("T") && parent.Age>18);

Parents类是一个自定义类,实现了IQueryProvider和IQueryable接口,但是只有Age属性被标记为检索,所以会处理Age属性,但是Name属性因为没有标记而被忽略。处理完 Age 属性后,我想将整个表达式传递给 LinqToObjects 进行处理和过滤,但我不知道如何。

注意它不需要删除表达式的 Age 子句,因为即使在我处理它之后结果也会相同,所以我总是能够将整个表达式发送到 LinqToObjects。

我已经尝试了以下代码,但它似乎不起作用:

IEnumerator IEnumerable.GetEnumerator() {       
    if(this.expression != null && !this.isEnumerating) {
        this.isEnumerating = true;
        var queryable=this.ToList().AsQueryable();
        var query = queryable.Provider.CreateQuery(this.expression);
        return query.GetEnumerator();
    }
    return this;
}

this.isEnumerating 只是一个布尔标志,用于防止递归。

this.expression 包含以下内容:

{value(namespace.Parents`1[namespace.Child]).Where(parent => ((parent.Name.EndsWith("T") AndAlso parent.Name.StartsWith("M")) AndAlso (parent.Test > 0)))}

当我单步执行代码时,尽管将结果转换为列表,但它仍然使用我的自定义类进行查询。所以我想,因为类 Parent 位于表达式的开头,它仍在将查询路由回我的提供程序,所以我尝试将 this.expression 设置为方法调用的 Argument[1] ,所以它是这样的:

{parent => ((parent.Name.EndsWith("T") AndAlso parent.Name.StartsWith("M")) AndAlso (parent.Test > 0))}

在我看来更像它,但是,每当我将它传递给 CreateQuery 函数时,我都会收到此错误“参数表达式无效”。

表达式的节点类型现在是 'Quote' 而不是 'Call' 并且方法为 null。我怀疑我只需要以某种方式使这个表达式成为调用表达式,它就会起作用,但我不知道该怎么做。

请记住,此表达式是 where 子句,但它可以是任何类型的表达式,我不希望在将表达式传递给 List 查询之前尝试分析表达式以查看它是什么类型提供者。

也许有一种方法可以将原始表达式的 Parent 类剥离或替换为列表提供程序类,但仍使其处于可以作为表达式传入列表提供程序的状态,而不管表达式的类型如何?

对此的任何帮助将不胜感激!

【问题讨论】:

  • 那么,您希望在从数据库中检索结果后,针对 Linq2Db 评估部分表达式,然后针对 Linq2Objects 评估部分表达式?
  • 哈哈,我写了这么多,而你用一句话概括了它……哦!你是对的,这就是我想要做的,但我不一定只需要将部分表达式转到 Linq2Objects,如果它更容易的话,它可以是全部。
  • 在我看来,使用两个参数的方法会更好;否则,我想您需要检查表达式并将其分成两个表达式;您将传递给IQueryable 的一个,然后在完全枚举该集合后,编译第二个表达式并将其传递给来自Linq 的Where 调用。听起来比我想的要容易。
  • 您需要调用某种聚合器,例如 (ToList) 以确保从数据库中提取结果。

标签: c# linq-to-objects iqueryable


【解决方案1】:

你离得太近了!

我的目标是避免不得不“复制”完整的令人费解的 SQL 到对象表达式功能集。你让我走上了正确的轨道(谢谢!)这里是如何在自定义 IQueryable 中搭载 SQL-to-Object:

public IEnumerator<T> GetEnumerator() {

    // For my case (a custom object-oriented database engine) I still 
    // have an IQueryProvider which builds a "subset" of objects each populated 
    // with only "required" fields, as extracted from the expression. IDs, 
    // dates, particular strings, what have you. This is "cheap" because it 
    // has an indexing system as well.

    var en = ((IEnumerable<T>)this.provider.Execute(this.expression));

    // Copy your internal objects into a list.

    var ar = new List<T>(en);
    var queryable = ar.AsQueryable<T>();

    // This is where we went wrong:
    // queryable.Provider.CreateQuery(this.expression);
    // We can't re-reference the original expression because it will loop 
    // right back on our custom IQueryable<>. Instead, swap out the first 
    // argument with the List's queryable:

    var mc = (MethodCallExpression)this.expression;
    var exp = Expression.Call(mc.Method, 
                    Expression.Constant(queryable), 
                    mc.Arguments[1]);


    // Now the CLR can do all of the heavy lifting
    var query = queryable.Provider.CreateQuery<T>(exp);
    return query.GetEnumerator();
}

不敢相信我花了 3 天时间才弄清楚如何避免在 LINQ-to-Object 查询上重新发明轮子。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多