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

时间:2014-07-25 18:32:03

标签: c# linq entity-framework

我有一个非常简单的扩展方法。 当尝试在Entity框架中使用它时,我得到了这个

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

许多其他人都遇到了同样的问题,我理解错误。总有办法让它发挥作用。

我一直在努力想弄清楚如何重新实现这种方法并让它成为EF友好的linq。

对于这个特定的方法,它只是检查一个IComparable是否在另外两个之间。所以它真的会扩展到

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

而我真正想做的就是让眼睛更容易,并表达出像

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

因为我对Func很新,所以我确定有一种方法可以接近扩展方法(即使专门为EF编写),这样他们就可以与EF linq友好了

我已经在SO和其他网站上进行了一些挖掘,并且遇到了一些有趣的答案,但却无法与他们一起走过终点线。

提前致谢!

2 个答案:

答案 0 :(得分:6)

查询提供程序的任务是获取您提供的表达式中提供的信息并将其转换为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方法,以便对不断变化的子表达式应用常用操作。

答案 1 :(得分:1)

您只能在Entity Framework已知道如何处理的表达式内的类型上使用一小部分函数。传统上这些是类似Contains和类似的方法(有关更完整的列表,请参阅this page)。如果解析器位于表达式中,则您自己的扩展方法将无法识别。

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

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

现在//...里面的内容需要我一个小时左右的时间来计算一切,并且在没有付费的情况下在互联网上快速回答是有点太多了。但是如果你学习如何解析和编写自己的表达方式(这是一项很好的技能),你可以自己弄清楚如何去做。

编辑:Or Servy can figure it out and post it我正在打字回答:)