在EF lambda表达式中使用扩展方法或Func [英] using Extension Method or Func in EF lambda expression

查看:482
本文介绍了在EF lambda表达式中使用扩展方法或Func的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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和其他站点上进行了一些挖掘,并遇到了一些有趣的答案,但是无法与他们脱颖而出.

提前谢谢!

解决方案

查询提供程序的任务是获取您提供的表达式中提供的信息,并将其转换为SQL.如果使用已有的代码并将其编译为C#方法,则查询提供程序将无法对其进行检查以查看原始源代码是什么,也无法使用该代码来创建相应的SQL代码.您需要使用一些它可以理解的创建Expression对象的方法,最简单的方法通常是通过lambda.

我们在这里可以做的是为我们的查询创建一个新的扩展方法,该方法将接受查询,一个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方法可以在各种其他上下文中重复使用,以将经常使用的操作应用于不断变化的子表达式.

I have an extension method that is fairly simple. When trying to use it in an Entity framework I get this

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

Many others are getting the same issue and I understand the error. There is always a way to make it work.

I have been digging around trying to figure out how i can re-implement this method and have it be linq to EF friendly.

for this specific method its just checking if one IComparable is between two others. so really it would just expand to

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

and all i am really trying to do is make it easier on the eyes and express it like

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

Being that I am very new to the Func and such Im sure there is a way to approach extension methods (even if written specifically for EF) so that they will be friendly with EF linq

I have done some digging around SO and other sites and come across some interesting answers but not able to get to past the finish line with them.

Thanks in advance!

解决方案

The query provider is going to be tasked with taking the information provided in the expression that you give it and translating that into SQL. If you take the code that you have and compile it into a C# method then the query provider has no way of inspecting it to see what the original source code was and using that to create corresponding SQL code. You need to use some means of creating Expression objects that it can understand, the easiest means of doing this is generally through lambdas.

What we can do here is create a new extension method for our queries that will accept a query, an Expression that represents the date in question, along with the constant date values that they should be between. Using this we can construct our own expression that represents what the expression would have looked like had you manually typed out the comparisons in the lambda itself:

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);
}

Here we're using a Compose method. This method accepts one expression that maps a value to another, along with a second expression that maps that value to something else, and it creates a new expression that represents mapping the original value from the first expression to the result of the second. It can do this by replacing all uses of the parameter in the second expression with the body of the first expression:

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);
}

Here we're using a method to replace all instances of one expression with another. This can be done using the following helper method:

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);
    }
}


Now I know that this does seem like a lot of code, but the idea here is that you're not using all of this just to write this one method. Everything after that first method is a fundamental building block that you can use to manipulate expressions in reasonably straightforward (and in particular, statically typed) ways. The Compose method can be re-used in all sorts of other contexts to apply a frequently used operation on a constantly changing sub-expression.

这篇关于在EF lambda表达式中使用扩展方法或Func的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆