0

I am using ExpressionVisitor to convert type of Expression<Func<T1,bool>> to Expression<Func<T2,bool>> and it works very well. But i noticed that if expression contains another types because of navigation properties ExpressionVisitor cause exceptions..

Assume that i have two different classes.

Brand
- ID
- Name
- Models (navigation property)

Model
- ID
- BrandID
- Brand (navigation property)
- Name

These two classes have totaly equal DTO's which named with suffix "Info". So i have four classes Brand, BrandInfo, Model, ModelInfo

Now i have got an expression like below.

Expression<Func<Brand,bool>> = x=> x.Name.Contains("Test")

This expression can be converted to Expression<Func<BrandInfo,bool>> successfully with ExpressionVisitor. But if i want to convert an expression like below it generates exception.

Expression<Func<Brand,bool>> = x=> x.Models.Count(p=>p.Name.Contains("Model Test")) > 0)

Because ExpressionVisitor only works on Brand and BrandInfo class and Brand class contains IEnumerable<Model> property as Models, but target class BrandInfo contains IEnumerable<ModelInfo> property as Models. So when ExpressionVisitor tries to bind Models property, it throws exception.

I couldn't find a way to convert an expression that contains multiple types.

here is the code that i use to convert

 public static class MappingHelpers {

        public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
            this Expression<Func<TFrom, bool>> from) {
            if(from == null) return null;
            return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
        }

        public static Expression<Func<IQueryable<TTo>, IOrderedQueryable<TTo>>> Convert<TFrom, TTo>(
            this Expression<Func<IQueryable<TFrom>, IOrderedQueryable<TFrom>>> from) {
            if(from == null) return null;
            return ConvertImpl<Func<IQueryable<TFrom>, IOrderedQueryable<TFrom>>, Func<IQueryable<TTo>, IOrderedQueryable<TTo>>>(from);
        }

        private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
            where TFrom : class
            where TTo : class {
            // figure out which types are different in the function-signature
            var fromTypes = from.Type.GetGenericArguments();
            var toTypes = typeof(TTo).GetGenericArguments();
            if(fromTypes.Length != toTypes.Length)
                throw new NotSupportedException(
                    "Incompatible lambda function-type signatures");
            Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
            for(int i = 0;i < fromTypes.Length;i++) {
                if(fromTypes[i] != toTypes[i])
                    typeMap[fromTypes[i]] = toTypes[i];
            }

            // re-map all parameters that involve different types
            Dictionary<Expression, Expression> parameterMap
                = new Dictionary<Expression, Expression>();
            ParameterExpression[] newParams = GenerateParameterMap<TFrom>(from, typeMap, parameterMap);

            // rebuild the lambda
            var body = new TypeConversionVisitor<TTo>(parameterMap).Visit(from.Body);
            return Expression.Lambda<TTo>(body, newParams);
        }

        private static ParameterExpression[] GenerateParameterMap<TFrom>(Expression<TFrom> from,
            Dictionary<Type, Type> typeMap, 
            Dictionary<Expression, Expression> parameterMap
        ) where TFrom : class {
            ParameterExpression[] newParams =
                new ParameterExpression[from.Parameters.Count];
            for(int i = 0;i < newParams.Length;i++) {
                Type newType;
                if(typeMap.TryGetValue(from.Parameters[i].Type, out newType)) {
                    parameterMap[from.Parameters[i]] = newParams[i] =
                        Expression.Parameter(newType, from.Parameters[i].Name);
                }
                else {
                    newParams[i] = from.Parameters[i];
                }
            }
            return newParams;
        }


        class TypeConversionVisitor<T> : ExpressionVisitor {
            private readonly Dictionary<Expression, Expression> parameterMap;

            public TypeConversionVisitor(
                Dictionary<Expression, Expression> parameterMap) {
                this.parameterMap = parameterMap;
            }

            protected override Expression VisitParameter(ParameterExpression node) {
                // re-map the parameter
                Expression found;
                if(!parameterMap.TryGetValue(node, out found))                    
                    found = base.VisitParameter(node);
                return found;
            }
            public override Expression Visit(Expression node) {
                LambdaExpression lambda = node as LambdaExpression;
                if(lambda != null && !parameterMap.ContainsKey(lambda.Parameters.First())) {
                    return new TypeConversionVisitor<T>(parameterMap).Visit(lambda.Body);
                }
                return base.Visit(node);
            }

            protected override Expression VisitMember(MemberExpression node) {
                // re-perform any member-binding
                var expr = Visit(node.Expression);
                if(expr.Type != node.Type) {
                    if(expr.Type.GetMember(node.Member.Name).Count() > 0) {
                        MemberInfo newMember = expr.Type.GetMember(node.Member.Name)
                                                   .Single();
                        return Expression.MakeMemberAccess(expr, newMember);
                    }
                    else {
                        //This silly code had written to solve problems that differences between models caused.
                        return Expression.Equal(Expression.Constant(1, typeof(int)), Expression.Constant(1, typeof(int)));
                    }
                }
                return base.VisitMember(node);
            }
        }

      public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new {
                f,
                s = second.Parameters[i]
            }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
            return first.Compose(second, Expression.AndAlso);
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
            return first.Compose(second, Expression.OrElse);
        }
    }
    class ParameterRebinder : ExpressionVisitor {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) {
            return new ParameterRebinder(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p) {
            ParameterExpression replacement;
            if(map.TryGetValue(p, out replacement)) {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }
3
  • Well, the problem is that your code doesn't convert the Model p parameter to ModelInfo p. You will have to figure out some way to tell your code to do that.
    – svick
    Commented Apr 29, 2013 at 13:07
  • Yeah i figure out that problem but i couldn't find a way to resolve that. Do you have a suggestion? Commented Apr 29, 2013 at 17:23
  • I think the most straightforward solution would be if Convert() also accepted a list of pairs of Types that should be mapped.
    – svick
    Commented Apr 29, 2013 at 18:12

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.