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;
}
Related
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);
}
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);
}
I have an Expression<Func<Entity, string>> that can be either a property or a nested property accessor
y => y.SearchColumn
or
y => y.SubItem.SubColumn
I am building up an expression tree dynamically and would like to get an InvokeExpression like this
x => x.SearchColumn.Contains("foo");
x => x.Sub.SearchColumn.Contains("foo");
I'm pretty sure I can unwrap the Expression body, then partially apply the x ParameterExpression to it but I can't figure out the magical incantations to make this happen
So I have something like
Expression<Func<Entity, string>> createContains(Expression<Func<Entity, string>> accessor) {
var stringContains = typeof(String).GetMethod("Contains", new [] { typeof(String) });
var pe = Expression.Parameter(typeof(T), "__x4326");
return Expression.Lambda<Func<Entity, bool>>(
Expression.Call(
curryExpression(accessor.Body, pe),
stringContains,
Expression.Constant("foo")
)
, pe
);
}
static Expression curryExpression(Expression from, ParameterExpression parameter) {
// this doesn't handle the sub-property scenario
return Expression.Property(parameter, ((MemberExpression) from).Member.Name);
//I thought this would work but it does not
//return Expression.Lambda<Func<Entity,string>>(from, parameter).Body;
}
Edit:
Here is the full thing I'm trying to do along with the error
You need to do two things - first one you can use the same accessor.Body, but it will reference to incorrect Parameter, as you created a new one. Second one you need to write custom ExpressionVisitor that will replace all usage of original y ParameterExpression to a new created, so result expression will be compiled fine.
If you don't need to create new ParameterExpression, then you can just use the same accessor.Body and resent original ParameterExpression to a new tree.
So here is my test working copy with a new ParameterExpression - https://dotnetfiddle.net/uuPVAl
And the code itself
public class Program
{
public static void Main(string[] args)
{
Expression<Func<Entity, string>> parent = (y) => y.SearchColumn;
Expression<Func<Entity, string>> sub = (y) => y.Sub.SearchColumn;
var result = Wrap(parent);
Console.WriteLine(result);
result.Compile();
result = Wrap(sub);
Console.WriteLine(result);
result.Compile();
result = Wrap<Entity>((y) => y.Sub.Sub.Sub.SearchColumn);
Console.WriteLine(result);
result.Compile();
}
private static Expression<Func<T, bool>> Wrap<T>(Expression<Func<T, string>> accessor)
{
var stringContains = typeof (String).GetMethod("Contains", new[] {typeof (String)});
var pe = Expression.Parameter(typeof (T), "__x4326");
var newBody = new ParameterReplacer(pe).Visit(accessor.Body);
var call = Expression.Call(
newBody,
stringContains,
Expression.Constant("foo")
);
return Expression.Lambda<Func<T, bool>>(call, pe);
}
}
public class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression _target;
public ParameterReplacer(ParameterExpression target)
{
_target = target;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// here we are replacing original to a new one
return _target;
}
}
public class Entity
{
public string SearchColumn { get; set; }
public Entity Sub { get; set; }
}
PS: this example will work only if you have only one ParameterExpression in original query, otherwise visitor should differentiate them
UPDATE
Here is my working answer with your full example in update - https://dotnetfiddle.net/MXP7wE
You just need to a fix a couple of things:
Return type of your method should be Expression<Func<T, bool>>.
The 1st parameter to Expression.Call() should simply be accessor.Body.
The ParameterExpression parameter to the Expression.Lambda<Func<T, bool>>() method call should simply be set from the accessor's parameter.
Method:
Expression<Func<T, bool>> CreateContains<T>(Expression<Func<T, string>> accessor)
{
var stringContains = typeof(String).GetMethod("Contains", new[] { typeof(String) });
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
accessor.Body,
stringContains,
Expression.Constant("foo")
)
, accessor.Parameters[0]
);
}
I need to extend the Where method for IQueryable to something like this:
.WhereEx("SomeProperty", "==", "value")
I dont know if this is even possible, but i found this in SO : Unable to sort with property name in LINQ OrderBy
I tried this answer and it seems quite interesting (Ziad's answer):
using System.Linq;
using System.Linq.Expressions;
using System;
namespace SomeNameSpace
{
public static class SomeExtensionClass
{
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);
}
}
}
Is It possible to do the same with the Where method ?
Thanks.
Update :
I can now set an expression to pass to the Call method, but i get the following exception: "No generic method 'Where' 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."
here is my code:
public static IQueryable<T> WhereEx<T>(this IQueryable<T> q, string Field, string Operator, string Value)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, Field);
var val = Expression.Constant(Value);
var body = Expression.Equal(prop, val);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
Type[] types = new Type[] { q.ElementType, typeof(bool) };
var mce = Expression.Call(
typeof(Queryable),
"Where",
types,
exp
);
return q.Provider.CreateQuery<T>(mce);
}
Notice that i dont use the Operator argument yet, instead i use Equal for debugging purpose.
Can someone help me with this please ?
I did another post with a simplified question. the link is Here
public static IQueryable<T> WhereEx<T>(this IQueryable<T> q, string Field, string Operator, string Value)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, Field);
var val = Expression.Constant(Value);
var body = Expression.Equal(prop, val);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
return System.Linq.Queryable.Where(q, exp);
}
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.