Get Value of Property inside a Lambda Expression using Reflection - c#

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

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.

c# How to evaluate compareTo(..) in Expression which returns a string

I have similar Situation like here: How to declare a Linq Expression variable in order to have it processed as a dbParameter
But in my case, don't want to compare with 'equal' operator, I need to compare one string against another. How can I achieve this?
I tried something like this:
var comparsionString = "aaa";
ParameterExpression param = Expression.Parameter(typeof(ItemSearch), "s");
Expression prop = Expression.Property(param, "Id");
Expression<Func<string>> idLambda = () => comparsionString;
Expression searchExpr = Expression.GreaterThanOrEqual(prop, idLambda.Body);
Expression<Func<ItemSearch, bool>> myLambda =
Expression.Lambda<Func<ItemSearch, bool>>(searchExpr, param);
But unfortunately GreaterThanOrEqual is not supported for strings. So I would need the string.CompareTo(..) method which is supported at least by SQL to Entities.
Say:
Expression searchExpr = Expression.IsTrue(*prop*.CompareTo(*idLambda*) >= 0);
How to write this in a way, that compiler can understand?
Any help is apreciated.
Thank you!
If you investigate the compiler translation for
Expression<Func<string, bool>> f = s => s.CompareTo("aaaa") >= 0;
You will discover you need to use GreaterThanOrEqual(Call("s", "CompareTo", "aaaa")) so the code to create that is:
var comparsionString = "aaa";
ParameterExpression param = Expression.Parameter(typeof(ItemSearch), "s");
Expression prop = Expression.Property(param, "Id");
Expression<Func<string>> idLambda = () => comparsionString;
var CallMethod = typeof(string).GetMethod("CompareTo", new[] { typeof(string) });
Expression callExpr = Expression.Call(prop, CallMethod, idLambda.Body);
Expression searchExpr = Expression.GreaterThanOrEqual(callExpr, Expression.Constant(0));
Expression<Func<ItemSearch, bool>> myLambda =
Expression.Lambda<Func<ItemSearch, bool>>(searchExpr, param);

The parameter '***' was not bound in the specified LINQ to Entities query expression

I am doing a common query in my project. I use Expression to build my query tree, the code list below:
public IList<Book> GetBooksFields(string fieldName, string fieldValue)
{
ParameterExpression paramLeft = Expression.Parameter(typeof(string), "m." + fieldName);
ParameterExpression paramRight = Expression.Parameter(typeof(string), "\"" + fieldValue + "\"");
ParameterExpression binaryLeft = Expression.Parameter(typeof(Book),"m");
BinaryExpression binaryExpr = Expression.Equal(paramLeft, paramRight);
var expr = Expression.Lambda<Func<Book, bool>>(binaryExpr, binaryLeft);
return bookRepository.GetMany(expr).ToList();
}
But when I invoke my GetBooksFields method, it will throw me an exception as below:
I debugged the expr variable and got the correct expression: {m => (m.Name == "sdf")}, it was what I want, But I don't know why I got the error,thx.
You can't "trick" LINQ into interpreting parameters as member-expressions by throwing in dots into variable names.
You'll have to construct the expression-tree correctly, as below
(EDIT: changed field to property as per your comment):
public IList<Book> GetBooksFields(string propertyName, string propertyValue)
{
var parameter = Expression.Parameter(typeof(Book), "book");
var left = Expression.Property(parameter, propertyName);
var convertedValue = Convert.ChangeType
(
propertyValue,
typeof(Book).GetProperty(propertyName).PropertyType
);
var right = Expression.Constant(convertedValue);
var binaryExpr = Expression.Equal(left, right);
var expr = Expression.Lambda<Func<Book, bool>>(binaryExpr, parameter);
return bookRepository.GetMany(expr).ToList();
}

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