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

标签 c# .net linq lambda expression

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

我有什么(举个例子来说明)

View 模型:

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小时一直在 sleep ,但是我对如何将其变成扩展方法一无所知。正如我所说,我想要的...基本上是一种写​​作方式:

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,它建议我将其转换为 bool 值...

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

更新 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。否则……好吧,最后的回复可能会消失。

最佳答案

要生成将被翻译成 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!");
}

关于c# - 通过比较返回 lambda 表达式的扩展方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10599831/

相关文章:

c# - 播放声音,Windows Phone 7

c# - 性能计数器的使用

c# - 如何判断是否已经设置了 out 参数?

.net - WiX 在装有 .NET 3.5 的机器上安装 .NET 4.5,但不在装有 .NET 4.0 的机器上

c# - IEqualityComparer 和单例

c# - 使用 Linq 搜索任何单词

.net - 设计Azure云日志框架

javascript - SignalR 异常 "Unrecognized user identity. The user identity cannot change during an active SignalR connection."

c# - 如何从 Xelement 中获取根元素

c# - ASP.NET EF6 查询模型