Convert expression tree from equal to contains - c#

In my server I currently have a method that checks if the field of an object equals a given string, and it looks like this:
var parameter = Expression.Parameter(typeof(Book), "b");
var predicate = Expression.Lambda<Func<Book, bool>>(Expression.Equal(Expression.PropertyOrField(parameter, filterAfter), Expression.Constant(filterField)),parameter);
To exemplify, if filterAfter is "title" and filterField is "Castle", then the expression will do book.title == "Castle". What I want to do is to convert the expression above to check if book.title contains the substring "Castle" in the title. How can I achieve that?

Use Expression.Call
var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] {typeof(string)});
var predicate = Expression.Lambda<Func<Book, bool>>(
Expression.Call(
Expression.PropertyOrField(parameter, filterAfter)
, containsMethod
, new Expression[] { Expression.Constant(filterField) }
)
, parameter
);

Related

Dynamically ordering by string

I've created an extension on IQueryable as I would like to order by nullable datetimes first then order by the datetime itself using just the string of the property i.e "activeTo". I've created the code below:
public static IQueryable<T> Sort<T>(this IQueryable<T> source, string sortBy)
{
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, sortBy);
var target = Expression.Constant(null, prop.Type);
var bin = Expression.Equal(prop, Expression.Convert(target, prop.Type));
var exp = Expression.Lambda(bin, param);
string method = "OrderBy";
Type[] types = new Type[] { source.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, source.Expression, exp);
//now do the ThenBy bit,sending in the above expression to the Expression.Call
exp = Expression.Lambda(prop, param);
types = new Type[] { source.ElementType, exp.Body.Type };
method = "ThenBy";
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types, orderByCallExpression, exp);
return source.Provider.CreateQuery<T>(ThenByCallExpression);
}
This extension is called by:
query.Sort("activeTo");
Which then gives the below the response:
{
"title": "test 5",
"activeFrom": "2019-06-08T21:26:50.2833333",
"activeTo": "2019-06-08T21:26:50.2833333",
},
{
"title": "test 2",
"activeFrom": "2019-06-08T21:28:45.65",
"activeTo": null,
}
I'd expect the record with activeTo as null to be first however, this isn't the case.
Does anyone know what I'm doing wrong?
From the comments the goal seems to be to dynamically generate an expression which sorts null values to the front.
The current code produces the following expression OrderBy(p => p.activeTo == null).ThenBy(p => p.activeTo == null). This has two flaws:
It sorts null values to the front as the bools sort order is false, true (as their ordinal values are 0 and 1, respectively). Therefore a comparison to null first collects the false cases, and then the truecases.
The ThenBy repeats the OrderBy, but was actually intended to emit ThenBy(p => p.ActiveTo).
The first can be solved by either using Expression.NotEqual instead of Expression.Equal for p => p != p.activeTo or by using OrderByDescending instead of OrderBy.
In total the code should be:
public static IQueryable<T> Sort<T>(IQueryable<T> source, string sortBy)
{
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, sortBy);
var target = Expression.Constant(null, prop.Type);
// NotEqual, to sort nulls before not-nulls
var bin = Expression.NotEqual(prop, Expression.Convert(target, prop.Type));
var exp = Expression.Lambda(bin, param);
// OrderBy with the null comparison expression
string method = nameof(Queryable.OrderBy);
Type[] types = new Type[] { source.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, source.Expression, exp);
// ThenBy with the property expression
exp = Expression.Lambda(prop, param);
types = new Type[] { source.ElementType, exp.Body.Type };
method = nameof(Queryable.ThenBy);
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types, orderByCallExpression, exp);
return source.Provider.CreateQuery<T>(ThenByCallExpression);
}
This yields the following expression:
OrderBy(p => p.activeTo != null).ThenBy(p => p.activeTo).
Remarks: It should be noted that usually OrderBy(p => p.activeTo) would already sort null values to the front, as this is the default sort order for strings, nullables, and so on. However, this behavior could be overwritten depending by the specific type and depend on the query source. Therefore, I left it like the OP has.

Get Value of Property inside a Lambda Expression using Reflection

I have an object called SearchDetails which contains:
SearchDetails:
{ ColName: "StrName"
SearchVal" "mega" }
I am making a generic lambda expression by using reflection method.
public dynamic searchMethod(object SearchDetails)
{
ParameterExpression Parameter = Expression.Parameter(typeof(SearchDetails), "x");
var searchCol = Expression.Property(
Parameter,
SearchDetails.GetType().GetProperty("ColName")
);
var colVal = Expression.Property(
Parameter,
SearchDetails.GetType().GetProperty("SearchValue").Name
);
Expression contMethod = Expression.Call(searchCol, "Contains", null, colVal);
Expression<Func<SearchDetails, bool>> lambda =
Expression.Lambda<Func<SearchDetails, bool>>(contMethod, Parameter);
return lambda;
}
The problem is that I am getting lambda expressions as follow:
{x => x.ColName.Contains(x.SearchValue)}
However, I want it to be like this: {x => x.StrName.Contains("megabrand")}.
I cannot access the value of the properties: ColName and SearchValue.
How to solve this problem?
What you are looking for is probably something similar to this:
public static Expression<Func<TSource, bool>> SearchMethod<TSource>(SearchDetails searchDetails)
{
ParameterExpression par = Expression.Parameter(typeof(TSource), "x");
var col = Expression.Property(par, searchDetails.ColName);
Expression body = Expression.Call(col, "Contains", null, Expression.Constant(searchDetails.SearchVal));
var lambda = Expression.Lambda<Func<TSource, bool>>(body, par);
return lambda;
}
Note that you have to pass the type of your table somewhere, in this case as a generic parameter TSource.
Use it like:
var search = new SearchDetails
{
ColName = "Foo",
SearchVal = "Bar",
};
var lambda = SearchMethod<TbStore>(search);
As an alternative you could use System.Linq.Dynamic.Core to obtain something similar.
var result = db.Where(searchDetails.ColName + ".Contains(#0)", searchDetails.SearchVal);

Build Any() with Expression Trees for LINQ Queries

I'm building a SQL "Any" clause dynamically using the System.Linq.Expressions.Expression class
I can do it like this
Expression<Func<User, Lead, bool>> predicate = (user, lead) => user.UserRoleSubProducts.Any(x => x.SubProductID == lead.SubProductID);
But I am not able to achieve this using Expression Tree.
I have tried below
var param1 = Expression.Parameter(typeof(User), "user");
var property1 = Expression.Property(param1, "UserRoleSubProducts");
var exp1 = Expression.Lambda(property1, new[] { param1 });
var param2 = Expression.Parameter(typeof(Lead), "lead");
var property2 = Expression.Property(param2, "SubProductID");
var exp2 = Expression.Lambda(property2, new[] { param2 });
var param3 = Expression.Parameter(property1.Type.GetProperty("Item").PropertyType, "x");
var property3 = Expression.Property(param3, "SubProductID");
var exp3 = Expression.Lambda(property3, new[] { param3 });
var equality = Expression.Equal(property2, property3);
var any = typeof(Queryable).GetMethods().Where(m => m.Name == "Any").Single(m => m.GetParameters().Length == 2).MakeGenericMethod(property1.Type);
var expression = Expression.Call(null, any, property1, equality);
But getting
Expression of type
'Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]' cannot be used for parameter of type System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]]' of method 'Boolean Any[DataServiceCollection1](System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]],
System.Linq.Expressions.Expression1[System.Func2[Microsoft.OData.Client.DataServiceCollection`1[Api.Models.UserRoleSubProduct],System.Boolean]])'
I think I am close enough. Any help is appreciated
Ignoring the redundant unused lambda expressions, the problem is in the last 2 lines.
First, you are using a wrong generic type (MakeGenericMethod(property1.Type)), while the correct type is basically the type of the parameter x here
.Any(x => x.SubProductID == lead.SubProductID)
=>
.Any<T>((T x) => ...)
which maps to param3.Type in your code.
Second, the second argument of the Any must be lambda expression (not simply equality as in the code).
Third, since user.UserRoleSubProducts most likely is a collection type, you should emit call to Enumerable.Any rather than Queryable.Any.
The Expression.Call method has overload which is very handy for "calling" static generic extension methods:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
So the last two lines can be replaced with:
var anyCall = Expression.Call(
typeof(Enumerable), nameof(Enumerable.Any), new Type[] { param3.Type },
property1, Expression.Lambda(equality, param3)
);

Expression predicates with several parameters

I use the code gave on Stackoverflow by Marc Gravell here :
http://goo.gl/57nW2
The code :
var param = Expression.Parameter(typeof (Foo));
var pred = Expression.Lambda<Func<Foo, bool>>(
Expression.Call(
Expression.PropertyOrField(param, fieldName),
"StartsWith",null,
Expression.Constant(stringToSearch)), param);
Now, I'd like combine several argument, sample :
public void BuildPredicate(string[] typeSearch, string[] field, string searchValue)
{
//Content
//typeSearch = new string[] {"Contains", "StartsWith", "StartsWith" };
//field = new string[] { "FieldA", "FieldB", "FieldC" };
//FieldA contains searchValue and FieldB startWith searchValue and FieldC startWith searchValue
}
An idea ?
Thanks,
You can simply loop over all operations on all fields and build up a Expression tree containing an OrElse clause for each type/field combination
var expressions = from type in typeSearch
from field in fields
select Expression.Call(
Expression.PropertyOrField(param, field),
type, null,
Expression.Constant(stringToSearch));
Expression body = Expression.Constant(false);
foreach (Expression expression in expressions)
{
body = Expression.OrElse(body, expression);
}
var result = Expression.Lambda<Func<Foo, bool>>(body, param);
And as requested, an example including calls to ToUpper:
var expressions = from type in typeSearch
from field in fields
select Expression.Call(
Expression.Call(
Expression.PropertyOrField(param, field),
"ToUpper", null),
type, null,
Expression.Constant(stringToSearch));

Combined Expressions

I am new to using expressions and I am having some problems in an example I am working through.
What I am trying to achieve is to create an Expression which has 2 (or many) Expressions within.
For example:
public static Expression<Func<Occurrence, bool>> ReporterStartsWithAndClosed()
{
ParameterExpression occPar = Expression.Parameter(typeof(Occurrence));
MemberExpression recorderProp = Expression.Property(occPar, "Reporter");
MemberExpression fullnameProp = Expression.Property(recorderProp, "FullName");
ConstantExpression letter = Expression.Constant("A", typeof(string));
MethodInfo miStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
MethodCallExpression mCall = Expression.Call(fullnameProp, miStartsWith, letter);
MemberExpression oiProp = Expression.Property(occPar, "OccurrenceIncident");
MemberExpression statusProp = Expression.Property(oiProp, "OccurreceIncidentStatus");
MemberExpression nameProp = Expression.Property(statusProp, "Name");
ConstantExpression name = Expression.Constant("Closed", typeof(string));
BinaryExpression equalTo = Expression.Equal(name, nameProp);
return ...?
}
The question I have, is how to I combine these expressions to return the correct type for this method. I.e. what is the syntax for combining the logic for mCall and equalTo Expressions.
My initial thought were that I should be using BlockExpressions but I couldn't get this to work.
Any help would be greatly appreciated.
Thanks
David
So to do a logical AND, use the AndAlso() method to generate the expression. Then to finish off your method, you'll want to put this combined method into an lambda expression.
Just some hints, I'd avoid writing out the types in your declarations, it makes everything harder to read. Also, you could call a method by name by using this overload of Call() so no need to get the MethodInfo object for the method.
I'd put it together like this (untested):
public static Expression<Func<Occurrence, bool>> ReporterStartsWithAndClosed(
string letter = "A")
{
// occurrence =>
// occurrence.Reporter.FullName.StartsWith("A")
//
// occurrence.OccurrenceIncident.OccurrenceIncidentStatus.Name == "Closed"
var occurrence = Expression.Parameter(typeof(Occurrence), "occurrence");
var reporter = Expression.Property(occurrence, "Reporter");
var fullName = Expression.Property(reporter, "FullName");
var startsWithLetter = Expression.Call(
fullName,
"StartsWith",
null,
Expression.Constant(letter, typeof(string))
);
var incident = Expression.Property(occurrence, "OccurrenceIncident");
var status = Expression.Property(incident, "OccurrenceIncidentStatus");
var name = Expression.Property(status, "Name");
var equalsClosed = Expression.Equal(
name,
Expression.Constant("Closed", typeof(string))
);
var body = Expression.AndAlso(startsWithLetter, equalsClosed);
return Expression.Lambda<Func<Occurrence, bool>>(body, occurrence);
}

Categories

Resources