Linq to Entities - 自定义方法 (OrderBy) 在 lambda 语句中失败

Linq to Entities - Custom method (OrderBy) fails in lambda statement

我正在尝试为 Linq to Entities 编写自定义 OrderBy 扩展方法,我可以在其中根据参数对 ASC 或 DESC 进行排序。我的第一次尝试如下:

public static class LinqExtensions
{   
    public static IOrderedQueryable<TSource> OrderByExtension<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, bool isDescending = true)
    {
        return (isDescending) ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);
    }
}

效果很好!...只要我不在 lambda 语句中使用此方法即可。一个玩具示例是:

using (var dbContext = new FooDBEntities())
{
    var foo = dbContext.Fubars.SelectMany(x => dbContext.Fubars.OrderByExtension(y => y.Foo, true)).ToList();
}

当我运行上面的代码时,程序崩溃了,我得到以下错误:

Unhandled Exception: System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IOrderedQueryable`1[ExpressionTreeFoo.Fubar] OrderByExtension[Fubar,String](System.Linq.IQueryable`1[ExpressionTreeFoo.Fubar], System.Linq.Expressions.Expression`1[System.Func`2[ExpressionTreeFoo.Fubar,System.String]], Boolean)' method, and this method cannot be translated into a store expression.

错误几乎说明了一切。该框架不知道如何将我的自定义扩展方法翻译成 SQL 可以理解的东西。

我一直在谷歌搜索,但我没有看到任何明确说明这是可能的或不可能的。所以我的问题是.. 有没有什么办法可以制作一个可以在 lambda 表达式中工作的自定义 OrderBy 方法(或不是扩展方法)?

这是因为 lambda 不是直接与 SQL 具有已知接口的编译表达式树。在您创建的意义上,lambda 是一种匿名 C# 方法,它只能由 CLR 执行。虽然它们共享相同的语法,但它们是框架的不同元素。如果您想实现自己的自定义 Linq-To-Entities 表达式,您需要研究构建 Expression Trees.

简单解释一下——这个怎么转:

(isDescending) ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);

进入这个?

SELECT * FROM [MyTable] ORDER BY [MyTable].[MyColumn] DESC

您必须正确定义如何使用 Linq 表达式树执行此操作。

Here 与您的问题类似(但没有答案) Here 是微软关于表达式树的文档

只有当你将它传递给 lambda 表达式时它才会失败的原因是因为你传递的内容无法编译成真正的表达式树

我终于找到了使用 NuGet 包的解决方案:LinqKit.EntityFramework

它的工作方式是先调用 .AsExpandable(),这会在 IQueryable 周围放置一个包装器。然后你必须调用 .Invoke 内部 lambda 表达式。 Invoke 是 LinqKit 的一个扩展方法。调用此查询时,LinqKit 将递归扫描抛出的表达式树,当它遇到调用扩展 Invoke 的表达式时,它将删除对 Invoke 的调用并以 CLR 引擎可以理解的方式重写表达式树。可以在以下位置找到完整的描述:http://www.albahari.com/nutshell/linqkit.aspx

static void EFTest()
{
    using (var dbContext = new DBEntities())
    {
        // debug - print query
        dbContext.Database.Log = Console.WriteLine;

        // get order by expression
        var orderByExpression = GetOrderByExpression(dbContext, true);

        // execute query
        var result = dbContext.FooTables
                              .AsExpandable()
                              .SelectMany(x => orderByExpression.Invoke(x))
                              .ToList();
    }
}
private static Expression<Func<FooTable, IOrderedQueryable<FooTable>>> GetOrderByExpression(DBEntities dbContext, bool isDesending)
{
    if (isDesending)
    {
        return x => dbContext.FooTables.OrderByDescending(y => y.Date);
    }
    else
    {
        return x => dbContext.FooTables.OrderBy(y => y.Date);
    }
}