I'm trying to build expressions dynamically for a rules engine and things were going very well until I tried to allow nested types and properties to be specified as operands. Sample:
ExpressionBuilder
public Expression BuildExpression<T>(string propertyName, Enums.Operator ruleOperator, object value, ParameterExpression parameterExpression)
{
ExpressionType expressionType = new ExpressionType();
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = MemberExpression.Property(body, member);
}
var leftOperand = MemberExpression.PropertyOrField(body, propertyName);
var rightOperand = Expression.Constant(Convert.ChangeType(value, value.GetType()));
FieldInfo fieldInfo = expressionType.GetType().GetField(Enum.GetName(typeof(Enums.Operator), ruleOperator));
var expressionTypeValue = (ExpressionType)fieldInfo.GetValue(ruleOperator);
return CastBuildExpression(expressionTypeValue, value, leftOperand, rightOperand);
}
RuleEngine
public Func<T, bool>[] CombineRules<T>(Criterion[] criteria)
{
List<Func<T, bool>> list = new List<Func<T, bool>>();
foreach (var criterion in criteria)
{
ExpressionBuilder expressionBuilder = new ExpressionBuilder();
var param = Expression.Parameter(typeof (T));
Expression expression = expressionBuilder.BuildExpression<T>(criterion.PropertyName,
criterion.Operator_, criterion.Value, param);
Func<T, bool> func = Expression.Lambda<Func<T, bool>>(expression, param).Compile();
list.Add(func);
}
return list.ToArray();
}
Criterion
public class Criterion
{
private bool propertySet;
public string PropertyName { get; set; }
public Enums.Operator Operator_ { get; set; }
public object Value { get; set; }
MemberModel
public class MemberModel
{
public string UserName{ get; set; }
public PersonalDetailsModel PersonalDetails {get; set;}
}
PersonalDetailsModel
public class PersonalDetailsModel
{
public int PersonalDetailsId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Middlename { get; set; }
public string DateOfBirth { get; set; }
public string GenderType { get; set; }
public string SalutationType { get; set; }
}
The problem arises when I try to pass in nested properties as a left operand, i.e. PropertyName="PersonalDetails.FirstName" into ruleEngine.CombineRules(criteria.ToArray());
I get "'PersonalDetails.Firstname' not a member of type 'System.String'", despite it clearly being so. I've been stuck on this for a while now, any idea what might be causing it?
Any help would be greatly appreciated.
Here
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = MemberExpression.Property(body, member);
}
you already processed the property path, so this
var leftOperand = MemberExpression.PropertyOrField(body, propertyName);
make no sense and is the source of the exception. In your example, body
contains something like p.PersonalDetails.FirstName
(String) and the above line is trying to build something like p.PersonalDetails.FirstName.PersonalDetails.FirstName
.
Use var leftOperand = body;
instead.
You can shorten the whole property path processing by using simple
var leftOperand = propertyName.Split('.')
.Aggregate((Expression)parameterExpression, Expression.PropertyOrField);