Problem reassigning internal Lambda in current LINQ search

I use Automapper and three-tier architecture in my application. I have a method that accepts a Linq (LambdaExpression) request as a parameter. To pass this parameter to another layer, I need to reassign it. So, I created an ExpressionExtension class that is responsible for returning a reassigned expression:

/// <summary>
/// A class which contains extension methods for <see cref="Expression"/> and <see cref="Expression{TDelegate}"/> instances.
/// </summary>
public static class ExpressionExtensions
{
    /// <summary>
    /// Remaps all property access from type <typeparamref name="TSource"/> to <typeparamref name="TDestination"/> in <paramref name="expression"/>.
    /// </summary>
    /// <typeparam name="TSource">The type of the source element.</typeparam>
    /// <typeparam name="TDestination">The type of the destination element.</typeparam>
    /// <typeparam name="TResult">The type of the result from the lambda expression.</typeparam>
    /// <param name="expression">The <see cref="Expression{TDelegate}"/> to remap the property access in.</param>
    /// <returns>An <see cref="Expression{TDelegate}"/> equivalent to <paramref name="expression"/>, but applying to elements of type <typeparamref name="TDestination"/> instead of <typeparamref name="TSource"/>.</returns>
    public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(
        this Expression<Func<TSource, TResult>> expression)
    {
        Contract.Requires(expression != null);
        Contract.Ensures(Contract.Result<Expression<Func<TDestination, TResult>>>() != null);

        var newParameter = Expression.Parameter(typeof(TDestination));
        Contract.Assume(newParameter != null);

        var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter);
        var remappedBody = visitor.Visit(expression.Body);
        if (remappedBody == null)
        {
            throw new InvalidOperationException("Unable to remap expression");
        }
        return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter);
    }
}

And the code for AutoMapVisitor:

/// <summary>
/// An <see cref="ExpressionVisitor"/> implementation which uses <see href="http://automapper.org">AutoMapper</see> to remap property access from elements of type <typeparamref name="TSource"/> to elements of type <typeparamref name="TDestination"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
    private readonly ParameterExpression _newParameter;
    private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();

    /// <summary>
    /// Initialises a new instance of the <see cref="AutoMapVisitor{TSource, TDestination}"/> class.
    /// </summary>
    /// <param name="newParameter">The new <see cref="ParameterExpression"/> to access.</param>
    public AutoMapVisitor(ParameterExpression newParameter)
    {
        _newParameter = newParameter;
    }

    /// <summary>
    /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
    /// </summary>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
    /// </returns>
    /// <param name="node">The expression to visit.</param>
    protected override Expression VisitMember(MemberExpression node)
    {
        var propertyMaps = _typeMap.GetPropertyMaps();
        // Find any mapping for this member
        var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member);
        if (propertyMap == null)
        {
            return base.VisitMember(node);
        }

        var destinationProperty = propertyMap.DestinationProperty;

        var destinationMember = destinationProperty.MemberInfo;

        // Check the new member is a property too
        var property = destinationMember as PropertyInfo;
        if (property == null)
        {
            return base.VisitMember(node);
        }

        // Access the new property
        var newPropertyAccess = Expression.Property(_newParameter, property);
        return base.VisitMember(newPropertyAccess);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T">Func of type TIn and result is TResult</typeparam>
    /// <param name="node">Expression of Func with certain input and result types</param>
    /// <returns></returns>
    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        //predicate.RemapForType<PersonViewModel, PersonDTO, bool>()
        // ,   1 () 
        var source = node.Parameters.FirstOrDefault();
        //     
        TypeMap typeMap = source == null
                              ? null
                              : Mapper.GetAllTypeMaps().FirstOrDefault(tMap => tMap.SourceType == source.Type);
        if (typeMap!=null)
        {
            var ts = typeMap.SourceType;
            var td = typeMap.DestinationType;
            var res = node.ReturnType;
            return VisitLambda(node, ts, td, res);
            //return node.RemapForType<ts,td,res>();
        }
        return base.VisitLambda<T>(node);
    }
    private Expression<Func<TOut,TResult>> VisitLambda<T,TIn, TOut, TResult>(Expression<T> node, TIn source, TOut destination, TResult result)
    {
        ParameterExpression pe = Expression.Parameter(typeof (TIn), "innerParam");
        var value = typeof(T) as Func<TIn, TResult>;
        Expression<Func<TIn, TResult>> input = Expression.Lambda<Func<TIn, TResult>>(Expression.Invoke(node.Body,node.Parameters)); // tried also without Expression.Invoke
        return input.RemapForType<TIn, TOut, TResult>();
    }
}

I understand that I need to override the base Lambda Visitor and call the ExpressionExtension.RemapForType method again to reassign the internal lambda. But how can I pass the correct parameters to it? So, I decided to create a general method, but nothing works correctly.

Could you help me find the right solution? For example, something like this:

Expression<Func<TSource,TResult>> a = val => val.FullName == "ABC" && val.SomeEnumerableProperty.Any(inner => inner.City == "Moscow");
// Here we will get remapped lambda with TDestination class as a parameter and TDestination class properties remapped by AutoMapper as you saw in my posted code
Expression<Func<TDestination, TResult>> b = a.RemapForType<TSource,TDestination,TResult>();

, , . , , .

, :

/// <summary>
/// An <see cref="ExpressionVisitor"/> implementation which uses <see href="http://automapper.org">AutoMapper</see> to remap property access from elements of type <typeparamref name="TSource"/> to elements of type <typeparamref name="TDestination"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
    private readonly TypeMap _typeMap;
    private readonly List<TypeMap> _typeMaps = new List<TypeMap>();
    private readonly Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
    private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>(); 

    /// <summary>
    /// Initialises a new instance of the <see cref="AutoMapVisitor{TSource, TDestination}"/> class.
    /// </summary>
    /// <param name="newParameter">The new <see cref="ParameterExpression"/> to access.</param>
    public AutoMapVisitor(ParameterExpression newParameter)
    {
        _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
        Contract.Assume(_typeMap != null);
        _typeMaps.Add(_typeMap);
        _typeMaps.AddRange(FillNestedTypeMaps(_typeMap));
        parameterMap.Add(newParameter.Type, newParameter); // main parameter which we don't need to recreate
        foreach (TypeMap map in _typeMaps)
        {
            if (parameterMap.ContainsKey(map.DestinationType)) continue;
            parameterMap.Add(map.DestinationType, Expression.Parameter(map.DestinationType, map.DestinationType.Name.ToLower()));
        }
    }

    /// <summary>
    ///            
    /// </summary>
    /// <param name="tMap"> ,        </param>
    private IEnumerable<TypeMap> FillNestedTypeMaps(TypeMap tMap)
    {
        List<TypeMap> result = new List<TypeMap>();
        // 1 where:    
        // 2 where:      ReverseMap ,   : A -> B,    A -> B  B -> A  
        // 3 where:   ,        ,    ,         
        IEnumerable<PropertyMap> pMaps = tMap.GetPropertyMaps();
        var list = Mapper.GetAllTypeMaps()
                         .Where(
                             map => map.SourceType.IsClass && map.DestinationType.IsClass)
                         .Where(map =>
                                !(map.Equals(tMap) ||
                                  (map.SourceType == tMap.DestinationType && map.DestinationType == tMap.SourceType)))
                         .Where(
                             map =>
                             pMaps
                                 .Any(
                                     pi =>
                                         {
                                             var pis = pi.SourceMember as PropertyInfo;
                                             if (pis == null) return false;
                                             bool forSource = pis.PropertyType == map.SourceType ||
                                                              (pis.PropertyType.IsGenericType &&
                                                               pis.PropertyType.GetGenericArguments()[0] == map.SourceType);
                                             bool forDestination = pi.DestinationPropertyType == map.DestinationType ||
                                                                   (pi.DestinationPropertyType.IsGenericType &&
                                                                    pi.DestinationPropertyType.GetGenericArguments()[0] == map.DestinationType);
                                             return forSource && forDestination;
                                         }))
                         .ToList();
        if (list.Count > 0)
        {
            result.AddRange(list);
            foreach (TypeMap typeMap in list)
            {
                result.AddRange(FillNestedTypeMaps(typeMap));
            }
        }
        return result;
    }

    private Type Map(Type type)
    {
        var tMap = _typeMaps.FirstOrDefault(map => map.SourceType == type);
        Contract.Assume(tMap != null);
        return tMap.DestinationType;
    }

    private ParameterExpression Map(ParameterExpression parameter)
    {
        var mappedType = Map(parameter.Type);
        ParameterExpression mappedParameter;
        if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
            parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
        return mappedParameter;
    }

    private Expression Map(MemberInfo mi, Expression exp)
    {
        Expression val;
        if (!memberMap.TryGetValue(mi, out val))
        {
            foreach (PropertyMap propertyMap in
                         _typeMaps.Select(map => map.GetPropertyMaps().SingleOrDefault(m => m.SourceMember == mi))
                                  .Where(propertyMap => propertyMap != null))
            {
                memberMap.Add(mi, val = Expression.PropertyOrField(exp, propertyMap.DestinationProperty.MemberInfo.Name));
                break;
            }
        }
        return val;
    }

    /// <summary>
    /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
    /// </summary>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
    /// </returns>
    /// <param name="node">The expression to visit.</param>
    protected override Expression VisitMember(MemberExpression node)
    {
        var expression = Visit(node.Expression);
        if (expression == node.Expression)
            return node;
        return Map(node.Member, expression);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
    }

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

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // if static object and generic method
        if (node.Object == null && node.Method.IsGenericMethod)
        {
            // Static generic method
            var args = Visit(node.Arguments);
            var genericArgs = node.Method.GetGenericArguments().Select(Map).ToArray();
            var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
            return Expression.Call(method, args);
        }
        return base.VisitMethodCall(node);
    }
}

:

variable 'person' of type 'Reestr.DAL.Entities.Person' referenced from scope '', but it is not defined

, , (, Any). , , ! , Enumerable , , . - , , ! !

@MBoros, Automapper (, ). ServiceStack.OrmLite !!! , , ...

+4

Source: https://habr.com/ru/post/1662646/


All Articles