c# Lambda Expression - Get property value from string [duplicate] - c#

This question already has answers here:
Get property value from string using reflection
(24 answers)
Closed 6 years ago.
Consider following lambda expression:
IQueryable<Product> query = query.Where(x => x.ProductName.Contains("P100"));
I need to convert above code something like this:
IQueryable<Product> query = query.Where(x => x.GetPropertyValue("ProductName").Contains("P100"));
Here I have added a dummy method GetPropertyValue("ProductName") to explain the requirement.
In above code the property should be resolved in run-time. In other words I need to access the property from a sting value E.g "ProductName"
How can I do this?

var parameterExp = Expression.Parameter(typeof(Product), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
Expression<Func<Product, bool>> predicate = Expression.Lambda<Func<T, bool>>
(containsMethodExp, parameterExp);
var query = query.Where(predicate);

You can have this extension method:
public static T GetPropertyValue<T>(this Product product, string propName)
{
return (T)typeof(Product).GetProperty(propName).GetValue(product, null);
}
Then:
IQueryable<Product> query = query.Where(x => x.GetPropertyValue<string>("ProductName").Contains("P100"));
Notice that this will not work with Entity Framework to query a database, but since you haven't tagged the question with entity framework, I'm not assuming you are using it

Related

How do I create a lambda expression using return value from another lambda expression? [duplicate]

This question already has an answer here:
Convert Linq expression "obj => obj.Prop" into "parent => parent.obj.Prop"
(1 answer)
Closed 4 years ago.
Neither the question nor the answer is the same as the question "Convert Linq expression “obj => obj.Prop” into “parent => parent.obj.Prop”". The only duplicate I've found on here lately is the number of duplicate mods with control issues.
I'm attempting to make a extension method for Entity Framework that will add a "Contains" call on a field if the given value is not null or whitespace. It's out of pure laziness of not wanting all the if statement checks for null or whitespace.
I want to be able to use it like this:
var qry = MyDb.Redacteds.OrderBy(a=>a.RedactedDate);
qry = qry.WhereContains(a => a.RedactedName, txtRedactedName.Text);
I've come up with this, but obviously the Invoke causes an issue with EF. What's the trick to using the result of a lambda when building an expression?
// create
// ent => exp(ent).Contains(s)
public static IQueryable<T> WhereContains<T>(this IQueryable<T> qry, Expression<Func<T, string>> exp, string s)
{
if (!string.IsNullOrWhiteSpace(s))
{
s = s.Trim();
var param = Expression.Parameter(typeof(T), "ent");;
var call = Expression.Invoke(exp, param); // <-= HERE
var body = Expression.Call(call, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant(s));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
qry = qry.Where(lambda);
}
return qry;
}
If it was simply a delegate instead of a lambda, the return could simple be:
ent => exp(ent).Contains(s)
That's what I'm looking to do.
You want to grab the parameter off the lambda and use the body as the expression. You aren't using it as a lambda at all - you are picking the expression tree and parameters out of the lambda.
// create
// ent => exp(ent).Contains(s)
public static IQueryable<T> WhereContains<T>(this IQueryable<T> qry, Expression<Func<T, string>> exp, string s)
{
if (!string.IsNullOrWhiteSpace(s))
{
s = s.Trim();
//HERE GRAB THE PARAMETER
var param = exp.Parameters[0];
//HERE JUST USE EXP.BODY
var body = Expression.Call(exp.Body, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant(s));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
qry = qry.Where(lambda);
}
return qry;
}

LINQ to Entities OrderBy Expression Tree

I am trying to write a LINQ query to orderBy a dynamic property given by a string value.
Here is what my original code was:
Expression<Func<T, dynamic>> orderBy = i => i.GetType().GetProperty("PropertyName").GetValue(null);
When I tried to run this orderBy I got the following exception:
LINQ to Entities does not recognize the method
'System.Object GetValue(System.Object)' method, and this method cannot
be translated into a store expression.
I am trying to work around this by creating an expression tree that will give me the same result. The code should be able to return any type depending on the parameter but I'm having trouble with the return type. If I don't convert the value I get a different error saying w Nullable DateTime can't be converted to Object. Here is the code that I have so far:
ParameterExpression pe = Expression.Parameter(typeof(T), "s");
Expression<Func<T, dynamic>> orderByExpression = Expression.Lambda<Func<T, dynamic>>(Expression.Convert(Expression.Property(pe, "PropertyName"), typeof(object)), pe);
and my new exception:
Unable to cast the type
'System.Nullable`1[[System.DateTime]]' to type
'System.Object'. LINQ to Entities only supports casting EDM primitive
or enumeration types.
How would I write this expression tree to do return a dynamic type? Also is there a better way I should be doing this in LINQ?
There are no template arguments for Expression<Func<T, TT>> which will allow you to represent both reference types and value types. Of course, you can build the expressions, but you must interact with them via reflection.
This will properly sort a collection:
IOrderedEnumerable<TEntityType> SortMeDynamically<TEntityType>(IEnumerable<TEntityType> query, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "s");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
if (methodCallExpression == null)
throw new Exception("Oops");
var method = methodCallExpression.Method.GetGenericMethodDefinition();
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
var orderedQuery = (IOrderedEnumerable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda.Compile() });
return orderedQuery;
}
Or if you want it on an IQueryable (if you're using EF, for example)
IOrderedQueryable<TEntityType> SortMeDynamically<TEntityType>(IQueryable<TEntityType> query, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "s");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
Expression<Func<IOrderedQueryable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
if (methodCallExpression == null)
throw new Exception("Oops");
var method = methodCallExpression.Method.GetGenericMethodDefinition();
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda });
return orderedQuery;
}

How to Create an Expression tree for .Where(x => x.<deep property>.Select(y => y.id).Intersect(List<int>).Any())

I'm creating a method that receives a Queryable<T> source, a string with a property name/path (could be a deep property for example "TrParent.DataTypes" to achieve this x => x.TrParent.DataTypes) and Enumerable<int> which holds the values I need to intersect.
Basically I come from the need to create the following query dynamically (I mean <DT_Det_Tr> and TrParent.DataTypes being know only at runtime, in the example DT_Det_Tr is not a type it is a class):
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
Please keep in mind that the preceding query is just an example of what I need to achieve dynamically, what I really need is an expression tree that creates a predicate like the one shown above but using a dynamic type and with the deep navigation property specified within a string.
So, I'm using this function to create the expression for the deep property:
private static LambdaExpression CreateDelegateExpression<T>(out Type resultingtype, string property, string parameterName = "x")
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type, parameterName);
Expression expr = param;
foreach (string prop in property.Split('.'))
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
resultingtype = type;
return lambda;
}
And here is what I have so far for my function:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
//List of ids
var _value = Expression.Constant(value);
//Get delegate expression to the deep property and it's inner type
Type type = null;
var lambda = CreateDelegateExpression<T>(out type, property, "x");
var enumtype = type.GetGenericArguments()[0];
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
MethodInfo innermethod = typeof(Queryable).GetMethods().Where(x => x.Name == "Select").First();
//Error on next line...
var selectCall = Expression.Call(typeof(Queryable),
"Select",
new Type[] { enumtype, typeof(long) },
lambda,
propExp);
//TODO: Add rest of logic and actually filter the source
return source;
}
In the var selectCall = line I'm getting error:
No generic method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
I've read a lot here on SO and other sites but I can't get past this part, I feel I'm going to bump into more trouble when I get to the .Intersect(List<int>).Any() part so any help on that also would be grand, thanks.
After a lot of thought, investigation and attempts I came up with a solution.
First, I made a simpler version of my goal query (the static example I used in my question), so instead of:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
I made this:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Any(y => _vals.Contains(y.Id))
);
Which is a lot easier to translate to expressions (or at least it was for me) because it omits the Select call.
I got rid of the method I was using to create the deep navigation property expression and streamlined it in my Intersect function, this was because it was doing some work I don't really need here plus I needed access to some of the variables I use inside it, then I made this:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
var type = typeof(T);
var _value = Expression.Constant(value); //List of ids
//Declare parameter for outer lambda
ParameterExpression param = Expression.Parameter(type, "x");
//Outer Lambda
Expression expr = param;
foreach (string prop in property.Split('.')) //Dig for deep property
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
//Get deep property's type
var enumtype = type.GetGenericArguments()[0];
//Declare parameter for inner lambda
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
//Inner Collection lambda logic
//Property for inner lambda
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
//Contains method call .Contains(y.Id)
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { propExp.Type }, _value, propExp);
//Create Expression<Func<enumtype, bool>>
var innerDelegateType = typeof(Func<,>).MakeGenericType(enumtype, typeof(bool));
//Create Inner lambda y => _vals.Contains(y.Id)
var innerFunction = Expression.Lambda(innerDelegateType, containsMethodExp, tpe);
//Get Any method info
var anyMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(enumtype);
//Call Any with inner function .Any(y => _vals.Contains(y.Id))
var outerFunction = Expression.Call(anyMethod, expr, innerFunction);
//Call Where
MethodCallExpression whereCallExpression = Expression.Call
(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(outerFunction, new ParameterExpression[] { param })
);
//Create and return query
return source.Provider.CreateQuery<T>(whereCallExpression);
}
I hope this helps anyone trying to develop a similar solution.
Working with expression trees can be very hard and frustrating at first, but it's a really powerful tool once you get the hold of it.
If you have access to the dynamic keyword from c# 4.0, you might be able to work around the problem like this:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where(obj => { dynamic x = obj;
return x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any();
}
);
But I don't know enough about the details of the problem you want to solve to say for sure.

Multiple linq query of order by to single one [duplicate]

This question already has answers here:
evaluate column name in linq where clause
(2 answers)
Closed 10 years ago.
I have multiple line condition for order by as below
if (enum1)
{
var = itemlist.orderby(r => r.column1)
}
else if (emum2)
{
var = itemlist.orderby(r => r.column2)
}
And so on.. Any way to do such thing dynamically.
What you can do is to better manage the column selection for sorting, e.g. using some collection that holds the Func<> for KeySelector.
E.g. if you have a class name 'SomeDTO' with four properties, Prop1,2,3 and 4. And four corresponding enum members.
var searchByMapping = new Dictionary<SearchByEnum,Func<SomeDTO, object>>();
searchByMapping.Add(SearchByEnum.Prop1, x => x.Prop1);
searchByMapping.Add(SearchByEnum.Prop2, x => x.Prop2);
searchByMapping.Add(SearchByEnum.Prop3, x => x.Prop3);
coll = coll.OrderBy(searchByMapping[searchByEnumParam]).ToList();
This is a not so dynamic approach but a typesafe one.
Supposing your enum can have only two values :
enum yourEnum( X=0, Y}
enum1 was somewhere set:
itemlist = enum1==Enum.X? itemlist.Orderby(r => r.column1): itemlist.Orderby(r => r.column2)
I did using below extension method.
public static class IQueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> items, string propertyName)
{
var typeOfT = typeof(T);
var parameter = Expression.Parameter(typeOfT, "parameter");
var propertyType = typeOfT.GetProperty(propertyName).PropertyType;
var propertyAccess = Expression.PropertyOrField(parameter, propertyName);
var orderExpression = Expression.Lambda(propertyAccess, parameter);
var expression = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { typeOfT, propertyType }, items.Expression, Expression.Quote(orderExpression));
return items.Provider.CreateQuery<T>(expression);
}
}

Building Expression Tree for string.Contains [duplicate]

This question already has answers here:
How do I create an expression tree to represent 'String.Contains("term")' in C#?
(4 answers)
Closed 9 years ago.
I'm struggling to build an expression tree so I can dynamically do filtering on some data.
I have come up with this, but it fails at the var lambda = line
foreach (var rule in request.Where.Rules)
{
var parameterExpression = Expression.Parameter(typeof(string), rule.Field);
var left = Expression.Call(parameterExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
var right = Expression.Constant(rule.Data.ToLower());
var method = typeof(string).GetMethod("Contains", new [] { typeof(string) });
var call = Expression.Call(left, method, right);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameterExpression);
query = query.Where(lambda);
}
The var rule has a Field (ex "Name") which I want to compare with the text in rule.Data (ex 'tom'). So if T.Name.Contains("tom"); I want the query to include the record, otherwise, not.
The var query is of type IQueryable<T>
EDIT: Finally got it working with this code:
foreach (var rule in request.Where.Rules)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, rule.Field);
var value = Expression.Constant(rule.Data);
var type = value.Type;
var containsmethod = type.GetMethod("Contains", new[] { typeof(string) });
var call = Expression.Call(property, containsmethod, value);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
query = query.Where(lambda);
}
You are almost there, but your parameter expression should be of type T, not String, you are also missing the expression that is getting the property from type T like name.
What you should roughly have is this
val -> Expression.Constant(typeof(string), rule.Field)
parameter -> Expression.Parameter(typeof(T), "p")
property -> Expression.Property(parameter, "PropertyName")
contains -> Expression.Call(property, containsmethod, val)
equals true -> Expression.True or equals, something like that
I am freehanding all of that, so it's likely somewhat different to be valid. The resulting expression should be something like this
p => p.Name.Contains(val)
If you want to create Where query, you must create lambda then call Where on query and pass lambda.
Try this:
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
MethodCallExpression expression = Expression.Call(typeof(Queryable), "Where",
new[] { typeof(T) }, query.Expression, lambda);
query = query.Provider.CreateQuery<T>(expression);
instead of
var result = Expression.IsTrue(call);
query = query.Provider.CreateQuery<T>(result);

Categories