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

Ner*_*roS 8 .net c# linq lambda expression

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

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

视图模型:

TotalCost (string) (value: "<50")
Required (string) (value: "5-10")
Run Code Online (Sandbox Code Playgroud)

EF型号:

TotalCost (double)
Required(double)
Run Code Online (Sandbox Code Playgroud)

我想要使​​用的表达式:

model => model.Where(field => field.TotalCost.Compare(viewModel.TotalCost) && field.Required.Compare(viewModel.Required));
Run Code Online (Sandbox Code Playgroud)

我希望收到的表达:

model => model.Where(field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10);
Run Code Online (Sandbox Code Playgroud)

或类似的东西

但是......我不知道从哪里开始.我把它缩小到了

public static Expression Compare<T>(this Expression<Func<T, bool>> value, string compare)
Run Code Online (Sandbox Code Playgroud)

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

有帮助吗?:X

更新:

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

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

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

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

    return d => d > int.Parse(arg);
}
Run Code Online (Sandbox Code Playgroud)

它首先缺少"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);
    }
Run Code Online (Sandbox Code Playgroud)

但是,我非常怀疑这就是我所追求的

更新3:

是的,在坐下来再次查看lambda表达式之后,在最后一个答案之前,我想出了类似于下面的内容,它没有填写"Compare()"的确切要求,但它是'overload-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);
    }
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

这显然是罪魁祸首:

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

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

The*_*est 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); }
}
Run Code Online (Sandbox Code Playgroud)

以下是使用示例.(假设我们有StackEntitesEF上下文,实体设置了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();
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:为了创建复杂的表达式,您可以使用以下代码:(假设已经创建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!");
}
Run Code Online (Sandbox Code Playgroud)


Aak*_*shM 5

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

Func<decimal, bool> totalCostIsUnder50 = d => d < 50m;
Run Code Online (Sandbox Code Playgroud)

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

Expression<Func<decimal, bool>> totalCostIsUnder50Expression = d => d < 50m;
Run Code Online (Sandbox Code Playgroud)

也就是说,使用 lambda 表达式来分配一个Expression表示Func? 它很整洁。

鉴于你说

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

我假设你可以在这里填空;假设我们将 `"<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
}
Run Code Online (Sandbox Code Playgroud)

最后,要将您的Expressions 与&&(并且仍然有Expression)组合在一起,请执行以下操作:

var andExpression = Expression.And(firstExpression, secondExpression);
Run Code Online (Sandbox Code Playgroud)