How Build Lambda Expression Tree with multiple conditions - c#

Note: I know it's much simple to create this using dynamic linq but I want to learn.
I want to create a lambda that "finds": Name=David AND Age=10.
class Person
{
public int Age { get; set; }
public string Name { get; set; }
}
var lambda = LabmdaExpression<Person>("Name", "David", "Age", 10);
static Expression<Func<T, bool>> LabmdaExpression<T>(string property1, string value1, string property2, int value2)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(Person), "o");
MemberExpression memberExpression1 = Expression.PropertyOrField(parameterExpression, property1);
MemberExpression memberExpression2 = Expression.PropertyOrField(parameterExpression, property2);
ConstantExpression valueExpression1 = Expression.Constant(value1, typeof(string));
ConstantExpression valueExpression2 = Expression.Constant(value2, typeof(int));
BinaryExpression binaryExpression1 = Expression.Equal(memberExpression1, valueExpression1);
BinaryExpression binaryExpression2 = Expression.Equal(memberExpression2, valueExpression2);
var ret1 = Expression.Lambda<Func<T, bool>>(binaryExpression1, parameterExpression);
var ret2 = Expression.Lambda<Func<T, bool>>(binaryExpression2, parameterExpression);
}

Expression andExpression = Expression.AndAlso(binaryExpression1, binaryExpression2);
return Expression.Lambda<Func<T, bool>>(andExpression , parameterExpression);
Edit - comment
You just chain together all your expresions
so in order to get this expression
X AND (Y OR (Z OR Q))
Expression ZorQ = Expression.OrElse(zExp, qExp);
Expression YorZorQ = Expression.OrElse(yExp, ZorQ);
Expression XandYorZorQ = Expression.AndAlso(xExp, YorZorQ);

Related

c# Expression Lambda to Func<T> from string

I try to implement e dynamic Funx expression from string
Expression<Func<CustomerDto, object>> expr = (src) => src.Customer.Id;
Func<CustomerDto, object> delg = expr.Compile();
var id = delg.Invoke(customerListDtos[0]);
and return the id (es. 123)
So now I try to create expression from string
public Expression<Func<T, object>> GetLambda<T>(string property)
{
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
foreach (var member in property.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
//return Expression.Lambda(body, param);
//Expression parent = Expression.Property(param, property);
Expression parent = Expression.Lambda(body, param);
//if (!parent.Type.IsValueType)
//{
// return Expression.Lambda<Func<T, object>>(parent, param);
//}
var convert = Expression.Convert(parent, typeof(object));
return Expression.Lambda<Func<T, object>>(convert, param);
}
So if I call the result from GetLambda the output is not 123 but is
var data = GetLambda<CustomerDto>("Customer.Id");
Func<CustomerDto, object> g = data.Compile();
var id = g(customerListDtos[0]);
the result is
{Method = {Int64 lambda_method(System.Runtime.CompilerServices.Closure, ef.Customer.CustomerDto)}}
You are calling Expression.Labmda twice, essentially wrapping one func into another. Try this instead:
public static Expression<Func<T, object>> GetLambda<T>(string property) {
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
foreach (var member in property.Split('.')) {
body = Expression.PropertyOrField(body, member);
}
var convert = Expression.Convert(body, typeof(object));
return (Expression<Func<T, object>>) Expression.Lambda(convert, param);
}

Construct expression tree with chained properties?

I have a method that accepts Expression<Func<T, string>>, for example x => x.Name, and a term, and returns x => x.Name.Contains(term):
Given the model;
class X
{
public Y Y {get; set;}
}
class Y
{
public string Z {get; set;}
}
It works well for GenerateForMember<Y>(y => y.Z, "foobar"), but currently don't work for GenerateForMember<X>(x => x.Y.Z, "foobar"). It gives the exception
'Z' is not a member of 'UserQuery+X'
How to I update my method to work with chained properties?
Method is as follows:
protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T,string>> expression, string term)
{
var type = typeof(T);
var memberExpression = ((expression.Body.NodeType == ExpressionType.Convert)
? ((UnaryExpression)expression.Body).Operand
: expression.Body) as MemberExpression;
ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
MemberExpression member = Expression.PropertyOrField(parameter, memberExpression.Member.Name);
var propertyInfo = memberExpression.Member as PropertyInfo;
var memberType = propertyInfo == null
? ((FieldInfo) memberExpression.Member).FieldType
: propertyInfo.PropertyType;
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(member, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
}
You are dissecting the original expression and later re-construct it again. This is not necessary. You can use expression.Body directly in order to create the method call. Like this, it should work with any lambda expression.
var type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
Try this:
protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T, string>> expression, string term)
{
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, expression.Parameters[0]);
}

MemberExpression, build Expression.Property from class

Below expression compares property NAME with the value PETER.
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression member = Expression.Property(pe, "name");
ConstantExpression value = Expression.Constant("Peter");
exp = Expression.Equal(member, value);
What if the property is a class:
public class Address
{
public string Name {get; set;}
}
Then the expression would look something similar to this:
MemberExpression member = Expression.Property(pe, "Address.Name");
ConstantExpression value = Expression.Constant("Peter");
exp = Expression.Equal(member, value);
This would fail because the member type doesn't match the value type.
So, the question is: How to build an Expression that would work using the above class sample ??
I use this expression in a NHibernate.Linq query:
var q = from f in data //of type IQueryable<T>
select f;
if (filter != null) //filter of type Expression<Func<T, bool>>
q = q.Where(filter);
etc....
Thank you.
UPDATE by Peter:
Based on the code from xanatos (next post) I have created the following test to understand how it works. Its not very different from what xanatos do, but at first I could not get it to work, so I decided to write it allover in one simple test, and that did it. With thanks to xanatos:
[Test]
public void FilterWithDeepProperties()
{
//Arrange
IGenericGridRepository repository = ObjectFactory.GetInstance<IGenericGridRepository>();
FilterDescriptor filter = new FilterDescriptor("AgreementId.Name", FilterOperator.IsEqualTo, "a name");
string[] properties = filter.Member.Split('.');
ParameterExpression pe = Expression.Parameter(typeof(SampleDomain), "x");
//Act
Expression lastMember = pe;
for (int i = 0; i < properties.Length; i++)
{
MemberExpression member = Expression.Property(lastMember, properties[i]);
lastMember = member;
}
ConstantExpression valueExpression = Expression.Constant(filter.Value);
Expression equalityExpression = Expression.Equal(lastMember, valueExpression);
Expression<Func<SampleDomain, bool>> where = Expression.Lambda<Func<SampleDomain, bool>>(equalityExpression, pe);
var result = repository.GetObjects<SampleDomain>(filter: where);
//Assert
result.Count().Should().BeGreaterThan(0, "because there are many schedule items equals to " + filter.Value);
}
You probably want something like:
public static Expression<Func<TSource, bool>> GetEquality<TSource>(object value, params string[] properties)
{
ParameterExpression pe = Expression.Parameter(typeof(TSource), "source");
Expression lastMember = pe;
for (int i = 0; i < properties.Length; i++)
{
MemberExpression member = Expression.Property(lastMember, properties[i]);
lastMember = member;
}
Expression valueExpression = Expression.Constant(value);
Expression equalityExpression = Expression.Equal(lastMember, valueExpression);
Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(equalityExpression, pe);
return lambda;
}
Use it like:
Expression exp = GetEquality<Person>("Foo", "Address", "Name");
Where Foo is your Peter (so the value that must be compared), while Address and Name are the names of the "chain" of properties. For example I'm using
public class Person
{
public Address Address { get; set; }
}
public class Address
{
public string Name { get; set; }
}
So the expression generated is
source.Address.Name == "Foo"
If you want to use something like Address.Name, you can use the method like
Expression exp = GetEquality<Person>("Foo", "Address.Name".Split('.'));

Convert Expression<Func<T, TProperty>> to Expression<Func<object, object>> and vice versa

Is there a way to convert the property selector Expression<Func<T, TProperty>> to Expression<Func<object, object>> and vice versa? I already know how to convert to Expression<Func<T, object>> using...
Expression<Func<T, TProperty>> oldExp;
Expression.Lambda<Func<T, object>>(Expression.Convert(oldExp.Body, typeof(object)), oldExp.Parameters);
... but I need to effectively cast both, the argument and the result of the function, not just e.g replace them with a ExpressionVisitor because they need to be casted back later.
You were correct that you need to use an ExpressionVisitor and ExpressionConvert.
Here are the two methods that you asked for (as well as some support methods):
public Expression<Func<object, object>> ConvertToObject<TParm, TReturn>(Expression<Func<TParm, TReturn>> input)
{
var parm = Expression.Parameter(typeof(object));
var castParm = Expression.Convert(parm, typeof(TParm));
var body = ReplaceExpression(input.Body, input.Parameters[0], castParm);
body = Expression.Convert(body, typeof(object));
return Expression.Lambda<Func<object, object>>(body, parm);
}
public Expression<Func<TParm, TReturn>> ConvertBack<TParm, TReturn>(Expression<Func<object, object>> input)
{
var parm = Expression.Parameter(typeof(TParm));
var castParm = Expression.Convert(parm, typeof(object));
var body = ReplaceExpression(input.Body, input.Parameters[0], castParm);
body = Expression.Convert(body, typeof(TReturn));
return Expression.Lambda<Func<TParm, TReturn>>(body, parm);
}
Expression ReplaceExpression(Expression body, Expression source, Expression dest)
{
var replacer = new ExpressionReplacer(source, dest);
return replacer.Visit(body);
}
public class ExpressionReplacer : ExpressionVisitor
{
Expression _source;
Expression _dest;
public ExpressionReplacer(Expression source, Expression dest)
{
_source = source;
_dest = dest;
}
public override Expression Visit(Expression node)
{
if (node == _source)
return _dest;
return base.Visit(node);
}
}
Sample usage:
Expression<Func<Customer, string>> expression = c => c.Name;
var convertedExpression = ConvertToObject<Customer, string>(expression);
var backExpression = ConvertBack<Customer, string>(convertedExpression);
Of course we can replace the original two methods with one method with more type parameters:
public Expression<Func<TTargetParm, TTargetReturn>> ConvertGeneric<TParm, TReturn, TTargetParm, TTargetReturn>(Expression<Func<TParm, TReturn>> input)
{
var parm = Expression.Parameter(typeof(TTargetParm));
var castParm = Expression.Convert(parm, typeof(TParm));
var body = ReplaceExpression(input.Body, input.Parameters[0], castParm);
body = Expression.Convert(body, typeof(TTargetReturn));
return Expression.Lambda<Func<TTargetParm, TTargetReturn>>(body, parm);
}
Sample usage:
Expression<Func<Customer, string>> expression = c => c.Name;
var convertedExpression = ConvertGeneric<Customer, string, object, object>(expression);
var backExpression = ConvertGeneric<object, object, Customer, string>(convertedExpression);

How to call property from property in c# expression?

I have 3 classes.
AutoYearMake{
int Year { get; set; }
string Make { get; set; }
}
AutoModel{
IAutoYearMake AutoYearMakeParent { get; set; }
string Model { get; set; }
}
AutoTrim{
IAutoModel AutoModelParent { get; set; }
string Trim { get; set; }
}
I need to create a query to a database. How can I get dynamically an expression like:
Expression<Func<AutoTrim, bool>> expression = expression = t => t.AutoModelParent.AutoYearMakeParent.Year == year.Value
&& t.AutoModelParent.AutoYearMakeParent.Make
== make && t.AutoModelParent.Model == model;
This is my code. It doesn't work.
ParameterExpression parameter = Expression.Parameter(typeof (AutoTrim), "a");
MemberExpression yearProp = Expression.Property(parameter, "AutoModelParent.AutoYearMakeParent.Year");
MemberExpression makeProp= Expression.Property(parameter, "AutoModelParent.AutoYearMakeParent.Make");
MemberExpression modelProp= Expression.Property(parameter, "AutoModelParent.Model");
Expression right = Expression.Constant(2014);
Expression e1 = Expression.Equal(yearProp, right);
right = Expression.Constant("make");
Expression e2 = Expression.Equal(makeProp, right);
right = Expression.Constant("model");
Expression e3 = Expression.Equal(modelProp, right);
Expression predicateBody = Expression.AndAlso(e1, e2);
Expression final = Expression.AndAlso(e1, e2);
How can I resolve this problem? I tried to use Expression.Call. It wasn't right way.
The first argument of the Expression.Property() methods is object from which property should be accessed. If you want to access property AutoModelParent from parameter t you use:
Expression.Property(parameter, "AutoModelParent")
So, if you want to access Model property of AutoModelParent property of parameter t you can go with:
Expression.Property(Expression.Property(parameter, "AutoModelParent"), "Model")
Consider this solution:
private static MemberExpression GetPropertyPathAccessor(Expression parameter, string path)
{
return (MemberExpression) path.Split('.').Aggregate(parameter, Expression.Property);
}
or if you don't like one-liners
private static MemberExpression GetPropertyPathAccessor(Expression parameter, string path)
{
Expression current = parameter;
foreach (var propertyName in path.Split('.'))
{
current = Expression.Property(current, propertyName);
}
return (MemberExpression)current;
}
Then you can use:
MemberExpression yearProp = GetPropertyPathAccessor(parameter, "AutoModelParent.AutoYearMakeParent.Year");
How about this?
After that you have the Func<> in expFunc and you can use it right away.
var argParam = Expression.Parameter(typeof(AutoTrim), "s");
var expFunc = Expression.Lambda<Func<AutoTrim, bool>>(
Expression.AndAlso(
Expression.AndAlso(
Expression.Equal(
Expression.Property(Expression.Property(Expression.Property(argParam, "AutoModelParent"), "AutoYearMakeParent"), "Year"),
Expression.Constant(year.Value)),
Expression.Equal(
Expression.Property(Expression.Property(Expression.Property(argParam, "AutoModelParent"), "AutoYearMakeParent"), "Make"),
Expression.Constant(make))
),
Expression.Equal(
Expression.Property(Expression.Property(argParam, "AutoModelParent"), "Model"),
Expression.Constant(model))
),
argParam
).Compile();
But of course you need these too (just example values):
int? year = 2000;
string make = "BMW";
string model = "6";

Categories

Resources