Метод расширения, возвращающий лямбда-выражение через compare

Я в процессе создания более сложной системы фильтрации для этого огромного нашего проекта. Одним из основных предикатов является возможность передавать сравнения через строковый параметр. Это выражается в следующей форме: ">50" или "5-10" или "

что у меня есть (в качестве примера для иллюстрации)

ViewModel:

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

ему не хватает" это-что - то-значение " для сравнения в первую очередь, и мне еще не удалось выяснить, как он сможет получить ввод выражения... что касается ReSharper, он предлагает мне вместо этого преобразовать его в boolean...

моя голова полна пуха в данный момент...

обновление 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:

правильно, после того, как сел и снова просмотрел лямбда-выражения, перед последним ответом я придумал что-то похожее на следующее, он не заполняет точные требования "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. Иначе... Ну, последний ответ, вероятно, пойдет.

3 ответов


чтобы создать выражение, которое будет переведено на SQL (eSQL), вы должны создать Expression вручную. Вот пример больше создание фильтра, другие фильтры можно сделать с подобным методом.

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 из 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();
    }
}

обновление: Для создания сложного выражения вы можете использовать такой код: (Предполагаю уже сделано CreateLessThanExpression и CreateBetweenExpression функции)

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# может сделать тяжелую работу за вас. Вы, вероятно, знаете, что вы можете сделать это:

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

то есть используйте лямбда-выражение для назначения Func. Но знаете ли вы, что вы можете и этого:

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

то есть используйте лямбда-выражение для назначения Expression что выражает Func? Это довольно аккуратно.

учитывая, что вы говорите

в сравнение builder не проблема, это легкий бит. Хард часть фактически возвращает выражение

Я предполагаю, что вы можете заполнить пробелы здесь; предположим, мы передадим ` "

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

трудная часть фактически возвращает выражение.

перевести строки в более структурированные конструкции, такие как перечисления и классы для определения свойств, операторов и фильтров:

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