Search code examples
c#linqexpressionexpandoobject

Using an ExpandoObject instead of the original entity in an expression


I have an expression like:

Expression<Func<MyEntity, bool>> exp = x => x.FirstName == "Jonas";

The expression is transferred to another application which do not have the type MyEntity.

To able to execute the expression I'm trying to replace the type in it to an ExpandoObject using an ExpressionVistor.

    public class ReplaceToExpandoVisitor : ExpressionVisitor
    {
        ParameterExpression _parameter;
        private Type _targetType = typeof(ExpandoObject);

        public ReplaceToExpandoVisitor(ParameterExpression p2)
        {
            _parameter = p2;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return _parameter;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
                throw new NotSupportedException();

            var memberName = node.Member.Name;
            var propBinder = Binder.GetMember(CSharpBinderFlags.None,
                memberName, 
                GetType(), 
                new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
            var inner = Visit(node.Expression);

            // this is the right way, right?
            var exp2 = Expression.Dynamic(propBinder, typeof(object), inner);

            // I need to convert it right? Otherwise it will be of type object?
            var propGetExpression = Expression.Convert(exp2, node.Type);

            return propGetExpression;
        }
    }

However, the expression returns false when being executed. So I guess that I'm not accessing the "property" in the expandoobject correctly.

Can someone epxlain what I'm doing wrong?


Solution

  • I think you are overcomplicating it with using binder. ExpandoObject implements IDictionary<string, object> interface so you can replace x => x.FirstName == "Jonas" with x => x["FirstName"] == "Jonas" which should be easier. Also you must override VisitLambda, to modify type params, otherwise convertion will fail.

    Here is example code:

    public class ReplaceToExpandoVisitor<TSource> : ExpressionVisitor
    {
        private static readonly PropertyInfo ItemProperty = typeof(IDictionary<string, object>).GetProperty("Item");
    
        private readonly ParameterExpression _targetParameter = Expression.Parameter(typeof(ExpandoObject));
    
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            var body = this.Visit(node.Body);
            var parameters = node.Parameters.Select(this.Visit).Cast<ParameterExpression>();
    
            return Expression.Lambda(body, parameters);
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (node.Type == typeof(TSource))
            {
                return this._targetParameter;
            }
    
            return node;
        }
    
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Member.MemberType != MemberTypes.Property)
            {
                throw new NotSupportedException();
            }
    
            string memberName = node.Member.Name;
    
            return Expression.Convert(
                Expression.Property(
                    this.Visit(node.Expression),
                    ItemProperty, 
                    Expression.Constant(memberName)
                ), 
                ((PropertyInfo)node.Member).PropertyType
            );
        }
    }
    

    Usage:

    Expression<Func<MyEntity, bool>> exp = x => x.FirstName == "Jonas";
    Expression<Func<ExpandoObject, bool>> exp2 = (Expression<Func<ExpandoObject, bool>>) new ReplaceToExpandoVisitor<MyEntity>().Visit(exp);
    
    dynamic obj = new ExpandoObject();
    obj.FirstName = "Jonas";
    
    bool result = exp2.Compile().Invoke(obj);