9

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

我有什么(作为一个例子来说明)

视图模型:

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

英孚型号:

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:

我设法找到一种方法,让一段代码在控制台应用程序的内存存储库中工作。不过,我还没有尝试使用实体框架。

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()”的确切要求,但它是一个“重载”哪里方法:

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。否则......好吧,最后的回应可能会消失。

4

3 回答 3

7

要生成表达式,您应该手动生成将其转换为 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实体集 TestEnitities 的 EF 上下文TestEntity

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!");
}
于 2012-05-15T18:05:36.400 回答
5

C# 编译器的一个乍看之下神奇的功能可以为您完成繁重的工作。你可能知道你可以这样做:

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

也就是说,使用 lambda 表达式来分配Func. 但是你知道你也可以这样做:

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

也就是说,使用 lambda 表达式来分配Expression表示 a 的 aFunc?这很整洁。

鉴于你说

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

我假设您可以在这里填写空白;假设我们将 `"<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的 s 与&&(并且仍然有一个Expression)组合在一起,请执行以下操作:

var andExpression = Expression.And(firstExpression, secondExpression);
于 2012-05-15T12:06:52.130 回答
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此处需要将两个表达式与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
于 2016-01-28T21:54:47.093 回答