在Linq查询中返回IQueryable的内部方法

3
我想在Entity Framework Code First中做以下事情:
Data.GroupBy(x => x.Person.LastName)
.Select(x => x.OtherGrouping(...)).Where(...).GroupBy(...)...etc



public static class Extensions
{

public static IQueryable<IGrouping<T, T>> OtherGrouping<T>(this IQueryable<T> collection /**** Here I want to pass consts and lambdas\expressions*****/)
    {
        // Grouping, filtration and other stuff here
        return collection.GroupBy(x => x);
    }
}

问题是.net编译OtherGrouping方法时,它不像表达式一样表示,并且无法转换为SQL。
我找到了LinqKit库,它应该在这种情况下有所帮助,但我无法弄清楚如何在我的特定情况下应用它。对于简单的情况,当我只有像x => x+2这样的表达式时,它可以正常工作,但我卡在了返回IQueryable上。可能可以完全手动编写表达式树,但我不想深入研究。
有什么想法如何完成或者我可以在哪里阅读相关内容?

这是我根据Rob的评论尝试做的

void Main()
{
    AddressBases.GroupBy(x => x.ParentId).Select(x => new {x.Key, Items = x.AsQueryable().OtherGrouping(a => a.Country) }).Dump();

}

public static class Extensions
{
    public static IQueryable<IGrouping<TKey, TSource>> OtherGrouping<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
    {   
        return source.Provider.CreateQuery<IGrouping<TKey, TSource>>(
            source.GroupBy(keySelector).Expression);
    }
}

我收到了一个异常:

NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[System.Linq.IGrouping`2[System.String,InsuranceData.EF.AddressBase]] OtherGrouping[AddressBase,String](System.Linq.IQueryable`1[InsuranceData.EF.AddressBase], System.Linq.Expressions.Expression`1[System.Func`2[InsuranceData.EF.AddressBase,System.String]])' method, and this method cannot be translated into a store expression.

我还无法确定为什么表达式树中有OtherGrouping方法?OtherGrouping方法只应该将另一个分组附加到表达式并将其传递给提供程序,而不是将自身放入树中。


1
你需要在集合的查询提供程序上调用该调用。这里有一个示例,请查看这里 - Rob
谢谢@Rob,看一下LINQ源代码是个好主意。因为我的愚蠢,我试图在EF源代码中寻找它并失败了。我会立即尝试,也许你可以发布答案而不是评论,这样我就可以接受它并且对其他人更加可见。 - Neir0
1个回答

1

您的当前代码在编辑后是正确的 - 您犯了一个常见问题,这是由C#提供的语法糖引起的。

如果您编写以下内容:

void Main()
{
    var res = Containers.OtherGrouping(c => c.ContainerID);
    res.Expression.Dump();
    res.Dump();
}

public static class Extensions
{
    public static IQueryable<IGrouping<TKey, TSource>> OtherGrouping<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
    {
        return source.Provider.CreateQuery<IGrouping<TKey, TSource>>(
            source.GroupBy(keySelector).Expression
        );
    }
}

您会看到我们得到输出:

Table(Container).GroupBy(c => c.ContainerID)

执行结果出现问题,虽然按照我们的谓词进行了正确的分组。但是,在将Select传递给它时,您在表达式中调用了OtherGrouping。让我们尝试一个简单的例子,不使用OtherGrouping

var res = Containers.OtherGrouping(c => c.ContainerID)
    .Select(x => new
    {
        x.Key,
        Items = x.GroupBy(c => c.ContainerID),
    });
res.Expression.Dump();

你提供给 Select 的是一个表达式树。也就是说,内部的 GroupBy 方法实际上从未被调用。如果你将查询更改为 x.AsQueryable().OtherGrouping 并在 OtherGrouping 中设置断点,它只会第一次被触发。
此外,x 是 IEnumerable<T>,而不是 IQueryable<T>。调用 AsQueryable() 可以得到一个 IQueryable,但它也会给你一个新的查询提供程序。我不确定 Entity Framework 的内部工作原理,但我敢打赌说这是无效的 - 因为我们不再将数据库作为提供程序的目标。事实上,使用 Linq2Sql(使用 LINQPad)时,我们会收到一个错误,指出不支持使用 .AsQueryable()
那么,你能做什么呢?不幸的是,没有干净的方法可以解决这个问题。由于表达式树在 OtherGrouping 有机会之前已经构建完成,因此 OtherGrouping 的主体是什么并不重要。我们需要在表达式树构建完成后但在执行之前修改表达式树。
为此,您需要编写一个表达式访问器,它将查找.OtherGrouping表达式调用,并将其替换为Queryable.GroupBy

可能我们可以这样做:OtherGrouping 应该返回一个表达式,然后我们需要为 Select 建立表达式,并通过 Invoke 调用 OtherGrouping 方法。然后将其传递给 Select,并使用 LinqKit 的 Expand 魔法将 Invoke 调用更改为实际表达式。 - Neir0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接