tools/AutoMapper/QueryableExtensions/Impl/QueryMapperVisitor.cs (130 lines of code) (raw):

using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace AutoMapper.QueryableExtensions.Impl { public class QueryMapperVisitor : ExpressionVisitor { private readonly IQueryable _destQuery; private readonly ParameterExpression _instanceParameter; private readonly Type _sourceType; private readonly Type _destinationType; private readonly Stack<object> _tree = new Stack<object>(); private readonly Stack<object> _newTree = new Stack<object>(); private readonly MemberAccessQueryMapperVisitor _memberVisitor; internal QueryMapperVisitor(Type sourceType, Type destinationType, IQueryable destQuery, IConfigurationProvider config) { _sourceType = sourceType; _destinationType = destinationType; _destQuery = destQuery; _instanceParameter = Expression.Parameter(destinationType, "dto"); _memberVisitor = new MemberAccessQueryMapperVisitor(this, config); } public static IQueryable<TDestination> Map<TSource, TDestination>(IQueryable<TSource> sourceQuery, IQueryable<TDestination> destQuery, IConfigurationProvider config) { var visitor = new QueryMapperVisitor(typeof(TSource), typeof(TDestination), destQuery, config); var expr = visitor.Visit(sourceQuery.Expression); var newDestQuery = destQuery.Provider.CreateQuery<TDestination>(expr); return newDestQuery; } public override Expression Visit(Expression node) { _tree.Push(node); // OData Client DataServiceQuery initial expression node type if (node != null && (int)node.NodeType == 10000) { return node; } var newNode = base.Visit(node); _newTree.Push(newNode); return newNode; } protected override Expression VisitParameter(ParameterExpression node) => _instanceParameter; protected override Expression VisitConstant(ConstantExpression node) { // It is data source of queryable object instance if (node.Value is IQueryable query && query.ElementType == _sourceType) return _destQuery.Expression; return node; } protected override Expression VisitBinary(BinaryExpression node) { var left = Visit(node.Left); var right = Visit(node.Right); // Convert Right expression value to left expr type // It is needed when PropertyMap is changing type of property if (left.Type != right.Type && right.NodeType == ExpressionType.Constant) { var value = Convert.ChangeType(((ConstantExpression)right).Value, left.Type, CultureInfo.CurrentCulture); right = Expression.Constant(value, left.Type); } return Expression.MakeBinary(node.NodeType, left, right); } protected override Expression VisitLambda<T>(Expression<T> node) { var newBody = Visit(node.Body); var newParams = node.Parameters.Select(p => (ParameterExpression)Visit(p)); var delegateType = ChangeLambdaArgTypeFormSourceToDest(node.Type, newBody.Type); var newLambda = Expression.Lambda(delegateType, newBody, newParams); return newLambda; } protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.Name == "OrderBy" || node.Method.Name == "OrderByDescending" || node.Method.Name == "ThenBy" || node.Method.Name == "ThenByDescending") { return VisitOrderBy(node); } var args = node.Arguments.Select(Visit).ToList(); var newObject = Visit(node.Object); var method = ChangeMethodArgTypeFormSourceToDest(node.Method); var newMethodCall = Expression.Call(newObject, method, args); return newMethodCall; } private Expression VisitOrderBy(MethodCallExpression node) { var query = node.Arguments[0]; var orderByExpr = node.Arguments[1]; var newQuery = Visit(query); var newOrderByExpr = Visit(orderByExpr); var newObject = Visit(node.Object); var genericMethod = node.Method.GetGenericMethodDefinition(); var methodArgs = node.Method.GetGenericArguments(); methodArgs[0] = methodArgs[0].ReplaceItemType(_sourceType, _destinationType); // for typical orderby expression, a unaryexpression is used that contains a // func which in turn defines the type of the field that has to be used for ordering/sorting if (newOrderByExpr is UnaryExpression unary && unary.Operand.Type.IsGenericType()) { methodArgs[1] = methodArgs[1].ReplaceItemType(typeof(string), unary.Operand.Type.GetGenericArguments().Last()); } else { methodArgs[1] = methodArgs[1].ReplaceItemType(typeof(string), typeof(int)); } var orderByMethod = genericMethod.MakeGenericMethod(methodArgs); return Expression.Call(newObject, orderByMethod, newQuery, newOrderByExpr); } protected override Expression VisitMember(MemberExpression node) => _memberVisitor.Visit(node); private MethodInfo ChangeMethodArgTypeFormSourceToDest(MethodInfo mi) { if (!mi.IsGenericMethod) return mi; var genericMethod = mi.GetGenericMethodDefinition(); var methodArgs = mi.GetGenericArguments(); methodArgs = methodArgs.Select(t => t.ReplaceItemType(_sourceType, _destinationType)).ToArray(); return genericMethod.MakeGenericMethod(methodArgs); } private Type ChangeLambdaArgTypeFormSourceToDest(Type lambdaType, Type returnType) { if (lambdaType.IsGenericType()) { var genArgs = lambdaType.GetTypeInfo().GenericTypeArguments; var newGenArgs = genArgs.Select(t => t.ReplaceItemType(_sourceType, _destinationType)).ToArray(); var genericTypeDef = lambdaType.GetGenericTypeDefinition(); if (genericTypeDef.FullName.StartsWith("System.Func")) { newGenArgs[newGenArgs.Length - 1] = returnType; } return genericTypeDef.MakeGenericType(newGenArgs); } return lambdaType; } } }