在EF lambda表达式中使用扩展方法或函数

本文关键字:扩展 方法 函数 EF lambda 表达式 | 更新日期: 2023-09-27 18:15:08

我有一个相当简单的扩展方法。当试图在实体框架中使用它时,我得到这个

LINQ to Entities does not recognize the method 'Boolean Between[DateTime](System.DateTime, System.DateTime, System.DateTime, Boolean)' 

很多人都有同样的问题,我理解这个错误。总有办法让它成功。

我一直在研究如何重新实现这个方法,并使它对EF友好。

对于这个特定的方法,它只是检查一个icomcomparable是否在其他两个之间。所以它会展开为

.Where(x=> x.Date >= startDate && x.Date <= endDate)

我真正想做的是让它更容易被眼睛看到,并表达成

.Where(x=> x.Date.Between(startDate, endDate))

由于我对函数非常陌生,因此我确信有一种方法可以接近扩展方法(即使是专门为EF编写的),以便它们与EF linq

友好。

我在SO和其他网站做了一些挖掘,遇到了一些有趣的答案,但无法通过他们的终点线。

提前感谢!

在EF lambda表达式中使用扩展方法或函数

查询提供程序将负责获取表达式中提供的信息并将其转换为SQL。如果你把你拥有的代码编译成一个c#方法,那么查询提供程序就没有办法检查它来查看原始源代码是什么,并使用它来创建相应的SQL代码。您需要使用一些它可以理解的方法来创建Expression对象,最简单的方法通常是通过lambdas。

我们在这里可以做的是为我们的查询创建一个新的扩展方法,它将接受一个查询,一个表示所讨论的日期的Expression,以及它们之间应该存在的常数日期值。使用它,我们可以构建自己的表达式,表示如果您在lambda本身中手动键入比较,表达式将是什么样子:

public static IQueryable<T> WhereBetweenDates<T>(
    this IQueryable<T> query,
    Expression<Func<T, DateTime>> selector,
    DateTime startDate,
    DateTime endDate)
{
    var predicate = selector.Compose(date => date >= startDate && date <= endDate);
    return query.Where(predicate);
}

这里我们使用的是Compose方法。该方法接受将一个值映射到另一个值的表达式,以及将该值映射到其他值的第二个表达式,并创建一个新表达式,表示将第一个表达式的原始值映射到第二个表达式的结果。它可以通过用第一个表达式的主体替换第二个表达式中参数的所有用法来实现这一点:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");
    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

在这里,我们使用一个方法将一个表达式的所有实例替换为另一个表达式。这可以使用以下帮助器方法完成:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

现在我知道这看起来像很多代码,但这里的想法是,你不使用所有这些只是为了写一个方法。第一个方法之后的所有内容都是一个基本的构建块,您可以使用它以相当简单的方式(特别是静态类型)操作表达式。Compose方法可以在各种其他上下文中重用,以便在不断变化的子表达式上应用经常使用的操作。

你只能在Entity Framework已经知道如何处理的表达式内的类型上使用一小部分函数。传统上,这些是Contains和类似的方法(请参阅本页以获得更完整的列表)。如果您自己的扩展方法在表达式内,解析器将无法识别它们。

然而,你可以做的一件事是为IQueryable<T>做一个扩展方法,将你的范围作为参数。

public static IQueryable<T> Between(this IQueryable<T> query, Expression<Func<T, DateTime> selector, DateTime start, DateTime end)
{
    //...
}

现在,//...内部的内容将花费我一个小时左右的时间来弄清楚这一切,并且在没有报酬的情况下在互联网上写下快速答案有点太多了。但是,如果你学会了如何解析和编写自己的表达式(这是一项很好的技能),你可能能够弄清楚如何自己完成它。

编辑:或者Servy可以找出并张贴它,而我正在输入我的答案:)