How can I convert a lambda-expression between different (but compatible) models?

前端 未结 2 1758
醉话见心
醉话见心 2020-12-29 06:33

(based on an email conversation, now recorded for information sharing) I have two models used at different layers:

public class TestDTO {
    public int Cust         


        
相关标签:
2条回答
  • 2020-12-29 06:58

    You could use AutoMapper (no expression tree):

    Mapper.CreateMap<Test, TestDTO>();
    
    ...
    
    Func<TestDTO, bool> fc1 =
      (TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10;
    
    Func<Test, bool> fc2 =
      (Test t) => fc1(Mapper.Map<Test, TestDTO>(t));
    
    0 讨论(0)
  • 2020-12-29 07:10

    To do that, you'll have to rebuild the expression-tree completely; the parameters will need re-mapping, and all member-access that is now talking to different types will need to be reapplied. Fortunately, a lot of this is made easier by the ExpressionVisitor class; for example (doing it all in the general case, not just the Func<T,bool> predicate usage):

    class TypeConversionVisitor : 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;
        }
        protected override Expression VisitMember(MemberExpression node)
        {
            // re-perform any member-binding
            var expr = Visit(node.Expression);
            if (expr.Type != node.Type)
            {
                MemberInfo newMember = expr.Type.GetMember(node.Member.Name)
                                           .Single();
                return Expression.MakeMemberAccess(expr, newMember);
            }
            return base.VisitMember(node);
        }
    }
    

    Here, we pass in a dictionary of parameters to re-map, applying that in VisitParameter. We also, in VisitMember, check to see if we've switched type (which can happen if Visit involves a ParameterExpression or another MemberExpression, at any point): if we have, we'll try and find another member of the same name.

    Next, we need a general purpose lambda-conversion rewriter method:

    // allows extension to other signatures later...
    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 =
            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];
            }
        }
    
        // rebuild the lambda
        var body = new TypeConversionVisitor(parameterMap).Visit(from.Body);
        return Expression.Lambda<TTo>(body, newParams);
    }
    

    This takes an arbitrary Expression<TFrom>, and a TTo, converting it to an Expression<TTo>, by:

    • finding which types are different between TFrom / TTo
    • using that to re-map the parameters
    • using the expression-visitor we just created
    • and finally constructing a new lambda expression for the desired signature

    Then, putting it all together and exposing our extension method:

    public static class Helpers {
        public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
            this Expression<Func<TFrom, bool>> from)
        {
            return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
        }
    
        // insert from above: ConvertImpl
        // insert from above: TypeConversionVisitor
    }
    

    et voila; a general-purpose lambda conversion routine, with a specific implementation of:

    Expression<Func<Test, bool>> fc2 = fc1.Convert<TestDTO, Test>();
    
    0 讨论(0)
提交回复
热议问题