Extension method returning lambda expression through comparison

I am going to create a more complex filtering system for this huge project of ours. One of the main predicates is the ability to pass comparisons through a string parameter. This is expressed as: "> 50" or "5-10" or "<123.2"

What I have (as an example for illustration)

ViewModel:

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

EF Model:

 TotalCost (double) Required(double) 

The expression I would like to use:

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

The expression I would like to get:

 model => model.Where(field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10); 

Or something like this

However ... I do not know where to start. I narrowed it down to

 public static Expression Compare<T>(this Expression<Func<T, bool>> value, string compare) 

This may not be entirely correct, but that’s all I have. The comparison constructor is not a problem, it is a simple bit. The hard part actually returns an expression. I have never tried returning expressions as function values. So basically what I need to save is a field and returns a comparison expression, to a large extent.

Any help ?: X

Update:

Alas, this does not solve my problem. Maybe because I have been in the last 23 hours, but I have no idea how to do this using the extension method. As I said, I would like ... basically a way to write:

 var ex = new ExTest(); var items = ex.Repo.Items.Where(x => x.Cost.Compare("<50")); 

The way I configured this function (possibly completely wrong)

 public static Expression<Func<decimal, bool>> Compare(string arg) { if (arg.Contains("<")) return d => d < int.Parse(arg); return d => d > int.Parse(arg); } 

There is no "this -something-value" for comparison in the first place, and I have not yet been able to figure out how to get the ability to enter an expression ... as for ReSharper, this involves me converting to boolean instead ...

My head is full of fluff at the moment ...

Update 2:

I managed to find a way to have a code snippet that works in a memory repository in a console application. I have yet to try it with 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); } 

However, I doubt very much that what I am after

Update 3:

That's right, after you went down and looked again at lambda expressions, before the last answer I came up with something similar to the following: it does not fill out the exact requirements of "Compare ()", but "overload" is -ish 'Where is the method:

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

However, despite my eyes, everything seems logical, I get an exception at runtime:

 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) 

This is obviously the culprit's line:

 var lambda = Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString())))); 

I am very close to a solution. If I can get this error from my back, I believe that EF should be able to translate this into SQL. Otherwise ... well, the last answer will probably go.

+8
c # lambda expression linq
source share
3 answers

To generate an expression that translates to SQL (eSQL), you must generate Expression manually. Here is an example of creating a GreaterThan filter; other filters can be made using a similar technique.

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

Here is a usage example. (Suppose we have a StackEntites EF context with a set of TestEnitities of TestEntity objects)

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

Update: To create a complex expression, you can use the following code: (Assume that the CreateLessThanExpression and CreateBetweenExpression functions have already been executed)

 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!"); } 
+6
source share

One of the magic features of the C-1 C compiler can do the hard work for you. You probably know you can do this:

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

i.e. use a lambda expression to assign Func . But did you know that you can also do this:

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

i.e. use lambda expression to assign Expression that Func expresses ? This is pretty neat.

Given what you say

The comparison constructor is not a problem, it is an easy bit. The hard part actually returns an expression

I assume you can fill in the blanks here; suppose we go to `` <50 "in:

 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 } 

Finally, to build your Expression with && (and still have Expression ), do the following:

 var andExpression = Expression.And(firstExpression, secondExpression); 
+4
source share

The hard part actually returns an expression.

Translate strings into more structured constructions, such as enumerations and classes, to define properties, operators, and filters:

 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 

Then the function for creating the expression is defined:

 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 taken here you need to combine the two expressions with the AND operator.

Using:

 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 

It will create an expression exactly as shown in the example.

 field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10 
0
source share

All Articles