2012-05-15 1 views
8

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

То, что я (в качестве примера для иллюстрации)

ViewModel:

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

EF Модель:

TotalCost (double) 
Required(double) 

Expression, что я хотел бы использовать:

model => model.Where(field => field.TotalCost.Compare(viewModel.TotalCost) && field.Required.Compare(viewModel.Required)); 

Expression, что я хотел бы получить:

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, он предлагает мне преобразовать его в boolean вместо этого ...

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

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:

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

+0

Я думаю, что ваша Update2 часть не будет выполняться с SQL Server (EF). Вы попробовали? –

+0

Да, просто сделал. Как я думал, тбх. – NeroS

ответ

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 с множества сущностей 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!"); 
} 
+0

Это кажется более полезным. Я обновил исходный вопрос снова, с моим собственным частичным решением, которое ... ну, все еще не совсем функционирует. – NeroS

+0

Я реализовал функцию теперь в методе фильтрации. Есть несколько проблем с EF, хотя сначала «getter» не должен иметь (MemberExpression) перед ним. Это просто исключение. Во-вторых, количество регулярных выражений (которое легче всего исправить) немного отложено, оно должно быть (? \ d + (\. \ D *)?), Которое в вашем сообщении говорит, что ** требует ** a " "здесь. Он не согласуется с не десятичным значением. Но кроме этого (y) – NeroS

+0

О Regex: вы правы. Обновленный ответ с * {0,1} * quantifier (тот же, что и *? *). –

4

Одна из главных особенностей C# -компилятора, способных на первый взгляд, может сделать вам тяжелую работу. Вы, наверное, знаете, что вы можете сделать это:

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

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

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

то есть использовать лямбда-выражения, чтобы присвоить 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 
} 

Наконец, чтобы составить ваш Expression S вместе с && (и до сих пор в Expression), сделайте следующее:

var andExpression = Expression.And(firstExpression, secondExpression); 
+0

Не уверен, что это даже применимо к ситуации. Обновлено оригинальное сообщение с комментариями. – 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 принято here необходимо объединить два выражения с оператором 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 
Смежные вопросы