0

Expression<Func<T1,bool>>ExpressionVisitor を使用して型をに変換していますがExpression<Func<T2,bool>>、非常にうまく機能します。しかし、ナビゲーション プロパティ ExpressionVisitor が原因で、式に別の型が含まれていると例外が発生することに気付きました..

2 つの異なるクラスがあるとします。

Brand
- ID
- Name
- Models (navigation property)

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

これらの 2 つのクラスには、接尾辞「Info」が付いた完全に等しい DTO があります。だから私は4つのクラスを持っていますBrand, BrandInfo, Model, ModelInfo

今、私は以下のような表現を得ました。

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

Expression<Func<BrandInfo,bool>>この式は で正常に変換できますExpressionVisitor。しかし、以下のような式を変換したい場合、例外が発生します。

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

ExpressionVisitorのみで機能し、クラスとBrandクラスにはプロパティとしてが含まれますが、ターゲット クラス BrandInfo にはプロパティとしてが含まれます。したがって、プロパティをバインドしようとすると、例外がスローされます。BrandInfoBrandIEnumerable<Model>ModelsIEnumerable<ModelInfo>ModelsExpressionVisitorModels

複数の型を含む式を変換する方法が見つかりませんでした。

ここに私が変換するために使用するコードがあります

 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);
        }
    }
4

0 に答える 0