Метод расширения, возвращающий лямбда-выражение через 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