I want to create an expression for converting a property from an Enum to an Int.
I want to use DynamicExpressionParser.ParseLambda to parse nested properties:
public enum MyEnum { A, B };
public class OuterClass
{
public InnerClass Inner { get; set; }
public MyEnum Enum { get; set; }
}
public class InnerClass
{
public MyEnum Enum { get; set; }
}
DynamicExpressionParser.ParseLambda<OuterClass, int>(null, true, "Inner.Enum");
But this will throw an exception:
Expression of type 'Int32' expected (at index 0)
How can I convert the Enum to an integer inside the expression?
It works with the following method, but i cannot use nested properties there, so it will only work when I place the "Enum" property on the OuterClass:
public static Expression<Func<T, TProperty>> GetExpression<T, TProperty>(string propertyName)
{
// x =>
var parameter = Expression.Parameter(typeof(T));
// x.Name
var mapProperty = Expression.Property(parameter, propertyName);
// (object)x.Name
var convertedExpression = Expression.Convert(mapProperty, typeof(TProperty));
// x => (object)x.Name
return Expression.Lambda<Func<T, TProperty>>(convertedExpression, parameter);
}
GetExpression<OuterClass, int>("Enum");
I found a solution:
// Create expression for it-type OuterClass. Pass null for result type which will result in an expression with the result type of the actual enum type.
var enumExpression = DynamicExpressionParser.ParseLambda(typeof(OuterClass), null, "Inner.Enum");
// Convert the result type of the body to int which will result in a converted expression
var convertedBodyExpression = Expression.Convert(enumExpression.Body, typeof(int));
// Create the final expression
var expression = Expression.Lambda<Func<OuterClass, int>>(convertedBodyExpression, enumExpression.Parameters);
Related
In order to use it as a selector in a Grouping Clause at runtime, I want basically retrieve a property from an object T by passing its name. Example with this class:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
}
I want to use my method like this:
private void MyMethod(string propertyName)
{
var lambda = GetLambdaExpression(propertyName);
// The problem is to specify the Type at compile time...
var selector = GetLambdaSelector<DateTime>(lambda); // Seems mandatory to convert LambdaExpression to Expression<Func<Person, T>> to use in a grouping clause
var result = emps.GroupBy(
selector.Compile(),
p => p.Birthday,
(key, g) => new { PersonName = key, = g.ToList() }).AsQueryable();
}
Here are methods statements :
LambdaExpression GetLambdaExpression(string propertyName)
{
var type = typeof(Person);
var propertyInfo = type.GetProperty(propertyName);
var propertyType = propertyInfo.PropertyType;
var entityType = propertyInfo.DeclaringType;
var parameter = Expression.Parameter(entityType, "entity");
var property = Expression.Property(parameter, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, parameter);
return lambda;
}
Expression<Func<Person, T>> GetLambdaSelector<T>(LambdaExpression expression)
{
return (Expression<Func<Person, T>>)expression;
}
I'm stuck because I want to resolve the type (DateTimein the example) at runtime and not at compile time.
So how to call this method with type resolve at runtime:
GetLambdaSelector<?>(lambda); ?
Or maybe can I change the signature of LambdaExpression GetLambdaExpression(string propertyName) to Expression<Func<Person, T>> with reflection but I did not manage it for the moment. ?
Test classes
public class Foo
{
public Bar Bar { get; set; }
}
public class Bar
{
public string Baz { get; set; }
}
public class BindFoo
{
public string BarBaz { get; set; }
}
Snippet
Expression<Func<Foo, object>> baz = x => x.Bar.Baz;
var param = Expression.Parameter(typeof(Foo), "x");
var bindings = typeof(BindFoo)
.GetProperties()
.Select(x => Expression.Bind(x, (MemberExpression)baz.Body))
.OfType<MemberBinding>()
.ToArray();
var expression = Expression.Lambda<Func<Foo, object>>(
Expression.MemberInit(
Expression.New(typeof(BindFoo).GetConstructor(Type.EmptyTypes)),
bindings),
param);
var func = expression.Compile();
Throws the 'x' not defined error at expression.Compile() when the property is nested. How do I bind the nested property Bar.Baz?
The expression built in the code above is x => new BindFoo() {BarBaz = x.Bar.Baz}, which is what I want but I think the x.Bar.Baz had not been bound correctly.
The problem is not related to Bind and nested members, but the parameter of the dynamically created lambda expression.
Parameters bind by instance, not by name. Here
Expression<Func<Foo, object>> baz = x => x.Bar.Baz;
var param = Expression.Parameter(typeof(Foo), "x");
you defined a new parameter, but then trying to use baz.Body which is bound to it's own parameter.
The solution is to use the original parameter
Expression<Func<Foo, object>> baz = x => x.Bar.Baz;
var param = baz.Parameters[0];
or replace the baz.Parameters[0] with the new parameter using expression visitor.
I'm storing an update operation as thus:
class Update
{
public Expression MemberExpression { get; set; }
public Type FieldType { get; set; }
public object NewValue { get; set; }
}
For example:
var myUpdate = new Update
{
MemberExpression = (MyType o) => o.LastModified,
FieldType = typeof(DateTime?),
NewValue = (DateTime?)DateTime.Now
}
Then I'm trying to apply this update later (simplified):
var lambda = myUpdate.MemberExpression as LambdaExpression;
var memberExpr = lambda.Body as MemberExpression;
var prop = memberExpr.Member as PropertyInfo;
prop.SetValue(item, myUpdate.Value);
However, myUpdate.Value is a DateTime, not a DateTime?. This is because when you cast a nullable to an object, it either becomes null or boxes the value type.
Since (DateTime?)myUpdate.Value would work to get it back to the correct type, I tried to simulate this compile-time construct using Convert.ChangeType. However, it says that casting from DateTime to DateTime? is not possible.
What approach can I use here to get the object back into its original type?
Is there any way to store a nullable types, regular structs and regular objects in a single field, and get the exact thing stored into it back again?
If you know the type of your Expression when you create myUpdate, SetInfo() will properly set your data, even if boxing/unboxing.
public class Update
{
public Expression MemberExpression { get; set; }
public Type ClassType { get; set; }
public object NewValue { get; set; }
}
public class MyType
{
public DateTime? LastModified { get; set; }
}
void Main()
{
var myUpdate = new Update
{
MemberExpression =
(Expression<Func<MyType, DateTime?>>)((MyType o) => o.LastModified),
ClassType = typeof(MyType),
NewValue = DateTime.Now
};
// At a later point where we do not want to instantiate via strong typing
var item = Activator.CreateInstance(myUpdate.ClassType);
var lambda = myUpdate.MemberExpression as LambdaExpression;
var memberExpr = lambda.Body as MemberExpression;
var prop = memberExpr.Member as PropertyInfo;
prop.SetValue(item, myUpdate.NewValue);
item.Dump();
}
This properly outputs a DateTime value corresponding to when it was created without an exception.
Maybe you can use a generic class?
class Update<TField>
{
public Expression<Func<MyType, TField>> MemberExpression { get; set; }
public TField NewValue { get; set; }
}
Not sure if you can just use a plain Func<MyType, TField> delegate instead of the expression tree, for your use.
I was able to solve using expressions and no reflection too:
var lambda = update.MemberExpression as LambdaExpression;
var memberExpr = lambda.Body as MemberExpression;
if (memberExpr == null)
{
throw new NotSupportedException("Field expression must be a member expression");
}
// do a cast - this ensures nullable types work, for instance
var cast = Expression.Convert(Expression.Constant(update.Value), update.FieldType);
// assign the target member with the cast value
var assignment = Expression.Assign(memberExpr, cast);
// build a new lambda, no return type, which does the assignment
var newLambda = Expression.Lambda(typeof(Action<T>), assignment, lambda.Parameters[0]);
// compile to something we can invoke, and invoke
var compiled = (Action<T>)newLambda.Compile();
compiled(item);
And voila, the item is modified :)
public Expression GetValue<T>(T source, PropertyInfo property)
{
ParameterExpression fieldName = Expression.Parameter(pi.PropertyType, pi.Name);
Expression fieldExpr = Expression.PropertyOrField(Expression.Constant(source), pi.Name);
var exp = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T), fieldExpr.Type),
fieldExpr,
fieldName);
return exp;
}
Here is my calling function
public MvcHtmlString RenderModel(T model)
{
foreach (var prop in typeof(T).GetProperties())
{
var x = InputExtensions.TextBoxFor(h, (dynamic) GetValue<T>(model, prop));
}
return new MvcHtmlString(string.Empty);
}
Here is an example class:
public class Test
{
public int? ID { get; set; }
public string Name { get; set; }
public DateTime? Date{ get; set; }
}
I adopted GetValue from part of:
Member Expression cannot convert to object from nullable decimal
which seems to work quite well until you reach properties that are not of type object I get this error:
ParameterExpression of type 'System.Nullable`1[System.Int32]' cannot be used for delegate parameter of type 'Test'
I'm not really sure where I've gone wrong here, and what that error even means
If I understand correctly, you need property access lambda. Here is the correct version of GetValue<T> method:
public Expression GetValue<T>(T source, PropertyInfo pi)
{
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
body = Expression.PropertyOrField(body, pi.Name);
return Expression.Lambda(body, param);
}
If you want to make an Expression by providing a delegate (that in your question it is Func<T,..> ) then you have to provide the delegate type, body expression and parameter expressions too:
public Expression GetValue<T>(T source, PropertyInfo property)
{
return Expression.Lambda(
delegateType: typeof(Func<,>).MakeGenericType(typeof(T), pi.PropertyType),
body: Expression.PropertyOrField(Expression.Parameter(typeof(T), "x"), pi.Name),
parameters: Expression.Parameter(typeof(T), "x"));
}
I want to create dynamic lambda expressions so that I can filter a list using a set of filtering parameters. This is what I have so far:
The expression is built using the methods bellow, where T is the object type of the list
public static Expression<Func<T, bool>> GetExpression<T>(IList<DynamicFilter> filters)
{
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1)
exp = GetExpression<T>(param, filters[0]);
[...]
return Expression.Lambda<Func<T, bool>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, DynamicFilter filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
[...]
return Expression.Call(member, filterMethod, constant);
}
I then call
List<Example> list = ...;
var deleg = ExpressionBuilder.GetExpression<Example>(dynFiltersList).Compile();
list = list.Where(deleg).ToList();
This works just as expected with an object that contains only simple types, but if there are complex types inside, the code doesn't work anymore. For example, let's say I have a member of custom type Field inside the Example class and Field has a string property Value. If filter.PropertyName would be 'Field0' (of type Field), the code would work just fine, but if I have 'Field0.Value' I would get an obvious error stating that there is no property named 'Field0.Value' inside class Example.
I tried modifying the expression building method, like this:
MemberExpression member = null;
if (filter.PropertyName.Contains('.'))
{
string[] props = filter.PropertyName.Split('.');
ParameterExpression param1 = Expression.Parameter(typeof(T).GetProperty(props[0]).PropertyType, "t1");
member = Expression.Property(param1, props[0]);
}
else
{
member = Expression.Property(param, filter.PropertyName);
}
but then I got a Lambda parameter not in scope error when compiling the expression. I sort of understand why I get this error, but I don't know how to make this work.
Bottom line is I need to make the expression building method work recursively when forming the MemberExpression. I ultimately need to obtain a list = list.Where(deleg).ToList(); that translates to something like this list = list.Where(obj => obj.Field0.Value == 'something').ToList();
I've just started working with expressions so I don't really know too much in this area, but any help would be appreciated.
Thanks
I realize this is a fairly old post, but I had the exact same problem and found something close in an answer that Mark Gravell posted here. I just modified it slightly to meet my needs and below is the result:
private Expression GetDeepProperty(Expression parameter, string property)
{
var props = property.Split('.');
var type = parameter.Type;
var expr = parameter;
foreach (var prop in props)
{
var pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
return expr;
}
Implementation:
var method = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}, null);
var lambdaParameter = Expression.Parameter(typeof(TEntity), "te");
var filterExpression = Expression.Lambda<Func<TEntity, bool>> (
filters.Select(filter => Expression.Call(GetDeepProperty(lambdaParameter, filter.Property),
method,
Expression.Constant(filter.Value))).
Where(exp => exp != null).
Cast<Expression>().
ToList().
Aggregate(Expression.Or), lambdaParameter);
I'm trying to address
so that I can filter a list using a set of filtering parameters
not by using ExpressionBuilder but instead by using a generic Filter class.
public class Filter<T> where T: class
{
private readonly Predicate<T> criteria;
public Filter(Predicate<T> criteria)
{
this.criteria = criteria;
}
public bool IsSatisfied(T obj)
{
return criteria(obj);
}
}
First we need to have some classes.
public class Player
{
public string Name { get; set; }
public int Level { get; set; }
public enum Sex { Male, Female, Other };
public Weapon Weapon { get; set; }
}
public class Weapon
{
public string Name { get; set; }
public int MaxDamage { get; set; }
public int Range { get; set; }
public WeaponClass Class { get; set; }
public enum WeaponClass { Sword, Club, Bow }
}
Then we need a list of objects.
var graywand = new Weapon { Name = "Graywand", MaxDamage = 42, Range = 1, Class = Weapon.WeaponClass.Sword };
var scalpel = new Weapon { Name = "Scalpel", MaxDamage = 33, Range = 1, Class = Weapon.WeaponClass.Sword };
var players = new List<Player> {
new Player { Name = "Fafhrd", Level = 19, Weapon = graywand },
new Player { Name = "Gray Mouser", Level = 19, Weapon = scalpel },
new Player { Name = "Freddy", Level = 9, Weapon = graywand },
new Player { Name = "Mouse", Level = 8, Weapon = scalpel}
};
Then let's create a couple of filters and add those to a list.
var powerfulSwords = new Filter<Player>(p => p.Weapon.MaxDamage>35);
var highLevels = new Filter<Player>(p => p.Level>15);
var filters = new List<Filter<Player>>();
filters.Add(powerfulSwords);
filters.Add(highLevels);
Finally filter the list by those filters
var highLevelAndPowerfulSwords = players.Where(p => filters.All(filter => filter.IsSatisfied(p)));
var highLevelOrPowerfulSwords = players.Where(p => filters.Any(filter => filter.IsSatisfied(p)));
Only "Fafhrd" will be in highLevelAndPowerfulSwords and highLevelOrPowerfulSwords will contain all players but "Mouse".
Have a look at ExpressionVisitor as described here: Replacing the parameter name in the Body of an Expression
You could generate an expression for each element in filter and combine them into one single expression using the methods below:
public static Expression<Func<T, K>> CombineAnd<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
{
ParameterExpression firstParameter = a.Parameters.First();
Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
return Expression.Lambda<Func<T, K>>(Expression.And(a.Body, b1.Body), firstParameter);
}
public static Expression<Func<T, K>> CombineOr<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
{
ParameterExpression firstParameter = a.Parameters.First();
Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
return Expression.Lambda<Func<T, K>>(Expression.Or(a.Body, b1.Body), firstParameter);
}