Dynamic query - how to do it - c#

I am using the way to build dynamic queries (create a generic mechanism for MongoDB) from: https://michaelscodingspot.com/dynamic-queries/, but somehow it is not working. Below is the code:
public static IQueryable<T> Filter<T>(IQueryable<T> query, string propertyToFilter, string value)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T));
MemberExpression memberAccess = Expression.PropertyOrField(parameterExpression, propertyToFilter);
ConstantExpression exprRight = Expression.Constant(value);
BinaryExpression equalExpr = Expression.Equal(memberAccess, exprRight);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(equalExpr, parameterExpression);
return query.Where(lambda);
}
The error I received is: System.ArgumentNullException: 'Value cannot be null. (Parameter 'itemName')'
When I am using the normal way: query.where(p => p.GroupId == value) everything is working

Hmm, I figure out it:
It is missing the propertyToFilter in the ParameterException
public static IQueryable<T> Filter<T>(IMongoQueryable<T> query, string propertyToFilter, string value)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), propertyToFilter);
MemberExpression memberAccess = Expression.PropertyOrField(parameterExpression, propertyToFilter);
ConstantExpression exprRight = Expression.Constant(value);
BinaryExpression equalExpr = Expression.Equal(memberAccess, exprRight);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(equalExpr, parameterExpression);
return query.Where(lambda);
}

Related

How to order result dynamically

I am trying to apply sorting dynamically. I am using EntityFramework. I am passing sortorder and sortfield. Now I don't want to write conditions to order result by sortfield column. What I have tried is as below,
public static IOrderedQueryable<TSource> OrderByProperty<TSource, TKey>(this IQueryable<TSource> source, string property, string sortorder)
{
ParameterExpression param = Expression.Parameter(typeof(TSource), "t");
MemberExpression member = Expression.Property(param, property);
var ex = Expression.Lambda<Func<TSource, TKey>>(member, param);
return source.OrderBy<TSource, TKey>(ex);
}
And I call it as below,
OrderByProperty<Class, dynamic>(objClass, sortfield, sortorder);
I am getting below error when sortfield is of type System.Int32;
Expression of type 'System.Int32' cannot be used for return type 'System.Object'
same for string. Any help is appreciated.
Try this extension method:
public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}

Merge two linq expressions

I have two expressions that are built out at separate times, but need to be merged in order to get an accurate 'grouping' of a where clause. I did try this option, but I am using Entity Framework and it doesn't understand the Invoke function. I have seen some of the ExpressionVisitor alternative but I do not think I have a good enough understanding of what I need to do.
If anyone could please point me in the right direction I would much appreciate it, it feels like it is close to there.
Where Clause 1A (object type Expression<Func<T, bool>>)
{parm => parm.Name.Contains("hat")}
Where Clause 1B (object type LambdaExpression, but can use (Expression<Func<T, bool>>)LambdaExpression )
{parm => parm.Attributes.Any(parm => ((parm.Name == "test") AndAlso (parm.Value == "21")))}
Needed Where Clause
{parm =>
parm.Name.Contains("hat") (&&/||)
parm.Attributes.Any(parm => ((parm.Name == "test") AndAlso (parm.Value == "21")))
}
If someone could please help me merge Where Clause 1A and Where Clause 1B, I would be very thankful..
Just an FYI Where() and Any() clause are generic methods obtained from IQueryable, not sure if that matters.
Func<MethodInfo, bool> methodLambda = m => m.Name == "Any" && m.GetParameters().Length == 2;
MethodInfo method = typeof(Queryable).GetMethods().Where(methodLambda).Single().MakeGenericMethod(ActualQueryableType);
ParameterExpression parentMember = Expression.Parameter(typeof(T), "parentParm");
// Create Any() Expression tree and convert it to lambda
MethodCallExpression callAny = Expression.Call(method, member, exp);
LambdaExpression lambdaAny = Expression.Lambda(callAny, param);
var combExp = parentExp.And((Expression<Func<T, bool>>)lambdaAny);
MethodCallExpression whereCall = Expression.Call(typeof(Queryable), "Where", new Type[] { query.ElementType }, new Expression[] {
query.Expression,
Expression.Quote(combExp)
});
query = (IQueryable<T>)query.Provider.CreateQuery(whereCall);
The error I get when using Invoke is:
NotSupportedException
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
Here's an implementation of PredicateBuilder that doesn't use Invoke:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
It instead uses a Replace method (implementation below) that replaces all instances of one expression with another.
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Using this you can now use And to AND together two predicate expressions that take the same input.

Casting the Expression.Property result

How do I create a cast when creating an Expression tree dynamically?
The problem is, I have a property of type string:
public class Test
{
public string Id { get; set; }
}
And I want to generically create a strongly typed lambda expression representing a delegate which returns an object instead of a string (Expression<Func<T, object>>).
Right now I am doing this:
private static Expression<Func<T, object>> CreateIdQuery()
{
Type type = typeof(T);
PropertyInfo idProperty = type.GetProperty("Id");
ParameterExpression lambdaParam = Expression.Parameter(type, "x");
MemberExpression body = Expression.Property(lambdaParam, idProperty);
LambdaExpression expr = Expression.Lambda(body, lambdaParam);
return (Expression<Func<T, object>>)expr;
}
But it throws an exception in the last line (I cannot cast Expression<Func<Test, string>> to Expression<Func<Test, object>>).
How do i cast the body of the expression (I am presuming the MemberExpression part needs to be cast into an object)?
Use Expression.Convert(body, typeof(object)).
private static Expression<Func<T, object>> CreateIdQuery()
{
Type type = typeof(T);
PropertyInfo idProperty = type.GetProperty("Id");
ParameterExpression lambdaParam = Expression.Parameter(type, "x");
MemberExpression body = Expression.Property(lambdaParam, idProperty);
UnaryExpression converted = Expression.Convert(body, typeof(object));
LambdaExpression expr = Expression.Lambda(converted, lambdaParam);
return (Expression<Func<T, object>>)expr;
}

Expression predicates with field name with a ToUpper

I have this code :
public static Expression<Func<T, bool>> CreatePredicate<T>(string typeSearch, string searchField, string stringToSearch)
{
var parameter = Expression.Parameter(typeof(T));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.Call(
Expression.PropertyOrField(parameter, searchField),
"Contains", null,
Expression.Constant(stringToSearch.ToUpper())), parameter);
return predicate;
}
the result is : {Param_0 => Param_0.Username.Contains("MX")}
But I'd like this : {Param_0 => Param_0.Username.ToUpper().Contains("MX")}
Thanks,
public static Expression<Func<T, bool>> CreatePredicate<T>(string typeSearch, string searchField, string stringToSearch)
{
var parameter = Expression.Parameter(typeof(T));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.Call(
Expression.Call(Expression.PropertyOrField(parameter, searchField), "ToUpper", null),
"Contains", null,
Expression.Constant(stringToSearch.ToUpper())), parameter);
return predicate;
}

Creating lambda expression ConstantExpression with a string value

I want to create lambda expression providing property name, a value (as string) and property type (as Type).
The problem with that is in line Expression.Constant(value1, propertyType);
value1 that is passed to Foo is string. and must be parsed to "unknown" type
static Expression<Func<T, bool>> LabmdaExpression<T>(string property1, string value1,
Type propertyType)
{
var parameterExpression = Expression.Parameter(typeof(TheObject), "o");
var memberExpression1 = Expression.PropertyOrField(parameterExpression, property1);
//casting?
var valueExpression1 = Expression.Constant(value1, propertyType);
var binaryExpression1 = Expression.GreaterThan(memberExpression1, valueExpression1);
return Expression.Lambda<Func<T, bool>>(binaryExpression1, parameterExpression);
}
I think you should have T where you currently have TheObject.
To convert the string, you can call the Convert.ChangeType() method in your expression and cast the resulting object:
static readonly MethodInfo ChangeTypeMethod = typeof(Convert).GetMethod(
"ChangeType", new[] { typeof(object), typeof(Type) });
static Expression<Func<T, bool>> LabmdaExpression<T>(
string property1, string value1, Type propertyType)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "o");
MemberExpression memberExpression1 = Expression.PropertyOrField(
parameterExpression, property1);
Expression convertedObject = Expression.Call(
ChangeTypeMethod, Expression.Constant(value1),
Expression.Constant(propertyType));
Expression converted = Expression.Convert(convertedObject, propertyType);
BinaryExpression binaryExpression1 = Expression.GreaterThan(
memberExpression1, converted);
return Expression.Lambda<Func<T, bool>>(binaryExpression1, parameterExpression);
}
Just casting won't work, because code like (int)"42" is not valid.

Categories