Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expression.PropertyOrField returns "not a member of type 'System.String'" for string property

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.

like image 892
shanomacadaemia Avatar asked Mar 25 '16 14:03

shanomacadaemia


1 Answers

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);
like image 68
Ivan Stoev Avatar answered Sep 22 '22 02:09

Ivan Stoev