通过比较返回lambda表达式的扩展方法

时间:2012-05-15 11:33:43

标签: c# .net linq lambda expression

我正在为我们这个庞大的项目创建一个更精细的过滤系统。其中一个主要谓词是能够通过字符串参数传递比较。这表现为以下形式:“> 50”或“5-10”或“< 123.2”

我所拥有的(作为一个例子来说明)

视图模型:

TotalCost (string) (value: "<50")
Required (string) (value: "5-10")

EF模型:

TotalCost (double)
Required(double)

我想使用的表达式:

model => model.Where(field => field.TotalCost.Compare(viewModel.TotalCost) && field.Required.Compare(viewModel.Required));

我希望收到的表达:

model => model.Where(field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10);

或类似的东西

然而......我不知道从哪里开始。我把它缩小到了

public static Expression Compare<T>(this Expression<Func<T, bool>> value, string compare)

它甚至可能不正确,但这是我所拥有的一切。比较构建器不是问题,这很容易。困难的部分实际上是返回表达式。我从未尝试将表达式作为函数值返回。基本上我需要保留的是字段并返回一个比较表达式。

有任何帮助吗? :X

更新

唉,这不能解决我的问题。这可能是因为我在过去的23个小时里一直在努力,但我对如何将它变成一种扩展方法没有丝毫的线索。正如我所说,我想要的......基本上是一种写​​作方式:

var ex = new ExTest();
var items = ex.Repo.Items.Where(x => x.Cost.Compare("<50"));

我塑造这个功能的方式(可能是完全错误的)是

public static Expression<Func<decimal, bool>> Compare(string arg)
{
    if (arg.Contains("<"))
        return d => d < int.Parse(arg);

    return d => d > int.Parse(arg);
}

它首先缺少“this -something- value”,而我还没有弄清楚如何让它能够获得表达式输入...至于ReSharper,它表明我把它转换为布尔值而不是......

此刻我的脑袋里满是绒毛......

更新2:

我设法找到了一种方法,可以在控制台应用程序的内存存储库中使用一段代码。我还没有尝试使用Entity Framework。

public static bool Compare(this double val, string arg)
    {
        var arg2 = arg.Replace("<", "").Replace(">", "");
        if (arg.Contains("<"))
            return val < double.Parse(arg2);

        return val > double.Parse(arg2);
    }

然而,我非常怀疑这就是我所追求的目标

更新3:

是的,在坐下来再次查看lambda表达式之后,在最后一个答案之前,我想出了类似于以下内容的东西,它没有填写“Compare()”的确切要求但它是一个'超载-ish'哪里方法:

public static IQueryable<T> WhereExpression<T>(this IQueryable<T> queryable, Expression<Func<T, double>> predicate, string arg)
    {
        var lambda =
            Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString()))));

        return queryable.Where(lambda);
    }

然而,尽管我的眼睛,一切看似合乎逻辑,我得到运行时例外:

System.ArgumentException was unhandled
  Message=Incorrect number of parameters supplied for lambda declaration
  Source=System.Core
  StackTrace:
       at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters)
       at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
       at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
       at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)

这显然是罪魁祸首:

var lambda =
                Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString()))));

我非常接近解决方案。如果我能从背后得到这个错误,我相信EF应该能够将其转换为SQL。否则......好吧,最后的反应可能会消失。

3 个答案:

答案 0 :(得分:6)

要生成表达式,将转换为SQL(eSQL),您应手动生成Expression。以下是 GreaterThan 过滤器创建的示例,其他过滤器可以使用类似的技术制作。

static Expression<Func<T, bool>> CreateGreaterThanExpression<T>(Expression<Func<T, decimal>> fieldExtractor, decimal value)
{
    var xPar = Expression.Parameter(typeof(T), "x");
    var x = new ParameterRebinder(xPar);
    var getter = (MemberExpression)x.Visit(fieldExtractor.Body);
    var resultBody = Expression.GreaterThan(getter, Expression.Constant(value, typeof(decimal)));
    return Expression.Lambda<Func<T, bool>>(resultBody, xPar);
}

private sealed class ParameterRebinder : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;

    public ParameterRebinder(ParameterExpression parameter)
    { this._parameter = parameter; }

    protected override Expression VisitParameter(ParameterExpression p)
    { return base.VisitParameter(this._parameter); }
}

以下是使用示例。 (假设我们有StackEntites EF上下文,实体设置为TestEntity实体的TestEnitities

static void Main(string[] args)
{
    using (var ents = new StackEntities())
    {
        var filter = CreateGreaterThanExpression<TestEnitity>(x => x.SortProperty, 3);
        var items = ents.TestEnitities.Where(filter).ToArray();
    }
}

更新: 为了创建复杂表达式,您可以使用以下代码: (假设已经CreateLessThanExpressionCreateBetweenExpression函数)

static Expression<Func<T, bool>> CreateFilterFromString<T>(Expression<Func<T, decimal>> fieldExtractor, string text)
{
    var greaterOrLessRegex = new Regex(@"^\s*(?<sign>\>|\<)\s*(?<number>\d+(\.\d+){0,1})\s*$");
    var match = greaterOrLessRegex.Match(text);
    if (match.Success)
    {
        var number = decimal.Parse(match.Result("${number}"));
        var sign = match.Result("${sign}");
        switch (sign)
        {
            case ">":
                return CreateGreaterThanExpression(fieldExtractor, number);
            case "<":
                return CreateLessThanExpression(fieldExtractor, number);
            default:
                throw new Exception("Bad Sign!");
        }
    }

    var betweenRegex = new Regex(@"^\s*(?<number1>\d+(\.\d+){0,1})\s*-\s*(?<number2>\d+(\.\d+){0,1})\s*$");
    match = betweenRegex.Match(text);
    if (match.Success)
    {
        var number1 = decimal.Parse(match.Result("${number1}"));
        var number2 = decimal.Parse(match.Result("${number2}"));
        return CreateBetweenExpression(fieldExtractor, number1, number2);
    }
    throw new Exception("Bad filter Format!");
}

答案 1 :(得分:4)

C#编译器的一目了然的神奇功能可以为您完成艰苦的工作。你可能知道你可以这样做:

Func<decimal, bool> totalCostIsUnder50 = d => d < 50m;

即,使用lambda表达式来指定Func。但是你知道你可以这样做:

Expression<Func<decimal, bool>> totalCostIsUnder50Expression = d => d < 50m;

也就是说,使用lambda表达式来分配表达Expression Func?它非常整洁。

鉴于你说

  

比较构建器不是问题,这很容易。很难   部分实际上是返回表达式

我假设你可以在这里填空;假设我们将“&lt; 50”传递给:

Expression<Func<decimal, bool>> TotalCostCheckerBuilder(string criterion)
{
    // Split criterion into operator and value

    // when operator is < do this:
    return d => d < value;

    // when operator is > do this:
    return d => d > value;

    // and so on
}

最后,要将Expression&&一起撰写(并且仍有Expression),请执行以下操作:

var andExpression = Expression.And(firstExpression, secondExpression);

答案 2 :(得分:0)

  

困难的部分实际上是返回表达式。

将字符串转换为更多结构化的构造,如枚举和类,以定义属性,运算符和过滤器:

Enum Parameter
    TotalCost
    Required
End Enum

Enum Comparator
    Less
    More
    Equals
End Enum

Class Criterion
    Public ReadOnly Parameter As Parameter
    Public ReadOnly Comparator As Comparator
    Public ReadOnly Value As Double

    Public Sub New(Parameter As Parameter, Comparator As Comparator, Value As Double)
        Me.Parameter = Parameter
        Me.Comparator = Comparator
        Me.Value = Value
    End Sub
End Class

然后定义了创建表达式的函数:

Function CreateExpression(Criteria As IEnumerable(Of Criterion)) As Expression(Of Func(Of Field, Boolean))
    Dim FullExpression = PredicateBuilder.True(Of Field)()

    For Each Criterion In Criteria
        Dim Value = Criterion.Value

        Dim TotalCostExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From {
            {Comparator.Less, Function(Field) Field.TotalCost < Value},
            {Comparator.More, Function(Field) Field.TotalCost > Value},
            {Comparator.Equals, Function(Field) Field.TotalCost = Value}
        }

        Dim RequiredExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From {
            {Comparator.Less, Function(Field) Field.Required < Value},
            {Comparator.More, Function(Field) Field.Required > Value},
            {Comparator.Equals, Function(Field) Field.Required = Value}
        }

        Dim Expressions As New Dictionary(Of Parameter, IDictionary(Of Comparator, Expression(Of Func(Of Field, Boolean)))) From {
            {Parameter.TotalCost, TotalCostExpressions},
            {Parameter.Required, RequiredExpressions}}

        Dim Expression = Expressions(Criterion.Parameter)(Criterion.Comparator)

        FullExpression = Expression.And(Expression)
    Next

    Return FullExpression
End Function

PredicateBuilder需要here才能将两个表达式与AND运算符组合在一起。

用法:

Function Usage() As Integer

    Dim Criteria = {
        New Criterion(Parameter.TotalCost, Comparator.Less, 50),
        New Criterion(Parameter.Required, Comparator.More, 5),
        New Criterion(Parameter.Required, Comparator.Less, 10)}

    Dim Expression = CreateExpression(Criteria)
End Function

它将创建与示例

中提供的完全相同的表达式
field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10