【问题标题】:using Extension Method or Func in EF lambda expression在 EF lambda 表达式中使用扩展方法或 Func
【发布时间】:2014-07-25 18:32:02
【问题描述】:

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

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

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

我一直在研究如何重新实现此方法并使其对 EF 友好。

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

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

而我真正想做的就是让眼睛看起来更轻松,并表达出来

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

由于我对 Func 非常陌生,因此我确信有一种方法可以处理扩展方法(即使是专门为 EF 编写的),以便它们对 EF linq 友好

我在 SO 和其他网站上进行了一些挖掘,发现了一些有趣的答案,但无法与他们一起越过终点线。

提前致谢!

【问题讨论】:

  • 如果它确实有效,那么继续使用第一个版本。 EF 在后台将您的查询转换为 SQL,因此自定义(或不​​受支持的)方法不能在 EF 查询中使用,因为 EF 提供程序不知道如何将它们正确转换为 sql..
  • @ethicallogics 解决方案不是在应用程序端而不是在数据库端做工作。这个问题的解决方案很糟糕。
  • @Selman22,第一种方法确实有效,我的一部分感觉必须有一种方法可以返回翻译。在我的扩展方法中构建一个 lambda express 并返回它或类似的东西
  • @ethicallogics,除了我试图不查询所有内容之外,它有效。我将使用的大多数表都有大量数据。谢谢你的信息

标签: c# linq entity-framework


【解决方案1】:

查询提供程序的任务是获取您提供给它的表达式中提供的信息并将其转换为 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 方法可以在各种其他上下文中重复使用,以对不断变化的子表达式应用常用操作。

【讨论】:

  • 对一切都很好的解释,非常有帮助。去实施,看看效果如何。看起来这正是我想要的。感谢您抽出宝贵时间!
  • 第二眼看这个用法似乎与我试图达到的有点不同。仍然有效,我很好奇是否有任何方法可以写出来,并且实际上有我在我的问题中发布它的方式 Where(x=>x.date.Between(x,y)
  • @workabyte 这与您要求的有点不同,尽管它相当相似。这是必然的。
【解决方案2】:

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

但是,您可以做的一件事是为 IQueryable&lt;T&gt; 创建一个扩展方法,将您的范围作为参数。

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 我正在输入我的答案:)

【讨论】:

  • 一旦你建立了一些基本的构建块来操作表达式,创建该方法的实现可以缩短到几秒钟。
  • @Servy 是的,在我点击提交 2 秒后,看到您提交的帖子确实让我的气球有点泄气。表达对我来说从来都不是最简单的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-12
  • 2010-12-20
  • 1970-01-01
  • 2015-03-12
  • 1970-01-01
相关资源
最近更新 更多