Вопрос по .net, linq, c# – Метод расширения, возвращающий лямбда-выражение через сравнение

8

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

Что я имею (как пример для иллюстрации)

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)

Возможно, это даже не правильно, но это все, что у меня есть. Построение сравнения - это не проблема, это легкая задача. Трудная часть на самом деле возвращает выражение. Я никогда не пытался вернуть выражения в виде значений функций. Так что, в основном, мне нужно сохранить поле и вернуть выражение сравнения.

Любая помощь? :Икс

Update:

Увы, это не решает мою проблему. Это может быть потому, что я проснулся последние 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, он предлагает вместо этого преобразовать его в логическое значение ...

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

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

Однако я очень сомневаюсь, что это то, что я после

Update 3:

Верно, после того, как я снова сел и посмотрел лямбда-выражения, перед последним ответом я придумал что-то похожее на следующее: оно не соответствует точным требованиям & quot; Сравнить () & quot; но это 'перегрузка-иш'; Где метод:

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. В противном случае ... ну, последний ответ, вероятно, пойдет.

Да, только что сделал. Как я бы подумал. NeroS
Я думаю, что ваша часть update2 не будет работать с SQL Server (EF). Вы пробовали это? The Smallest

Ваш Ответ

3   ответа
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 с набором сущностей TestEnititiesTestEntity лица)

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!");
}
Я полагаю, что проблема была вызвана Entity Framework. В общем тесте репозитория у меня тоже все получалось. NeroS
Это кажется более полезным. Я снова обновил исходный вопрос своим собственным частичным решением, которое ... ну, по-прежнему, не работает. NeroS
Я реализовал функцию сейчас в методе фильтрации. Однако есть несколько проблем с EF, во-первых, & quot; getter & quot; перед ним не должно быть (MemberExpression). Это просто исключение. Во-вторых, регулярное выражение числа (которое проще всего исправить) слегка отключено, оно должно быть (? & Lt; number & gt; \ d + (\. \ D *)?), Как говорится в вашем постеrequires ". номера" & quot; Вот. Он не соглашается на недесятичное значение. Но кроме этого (у) NeroS
О Regex: ты прав. Обновленный ответ с{0,1} квантификатор (такой же как?).
И оMemberExpression - попробовал этот код, работал как шарм.var filter = CreateFilterFromString<TestEnitity>(x => x.SortProperty, ">2"); Не могли бы вы привести пример, когда возникает исключение?
4

ать тяжелую работу за вас. Вы, вероятно, знаете, что можете сделать это:

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

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

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

то есть,use a lambda expression to assign an Expression that expresses a Func? Это довольно опрятно.

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

The comparison builder is not the issue, that's the easy bit. The hard part is actually returning the expression

Я предполагаю, что вы можете заполнить пробелы здесь; Предположим, мы передали в "& 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);
Не уверен, применимо ли это к ситуации. Обновлен оригинальный пост с комментариями. NeroS
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 = {
        Ne,w 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

Похожие вопросы