Translating expression tree from a type to another type with complex mappings

后端 未结 2 615
借酒劲吻你
借酒劲吻你 2020-12-10 07:42

inspired by this answer I\'m trying to map a property on a model class to an expression based on the actual entity. These are the two classes involved:

publi         


        
2条回答
  •  离开以前
    2020-12-10 08:43

    Another solution would be to use AutoMapper to map complex types and modify the resulting expression query with an ExpressionTransformer before it gets executed. I will try to explain with a complete sample:

    Model classes

    Some POCO classes just for holding data.

    public enum CostUnitType
    {
        None = 0,
        StockUnit = 1,
        MachineUnit = 2,
        MaintenanceUnit = 3
    }
    
    public class CostUnit
    {
        public string CostUnitId { get; set; }
        public string WorkplaceId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    
        public CostUnitType Type { get; set; }
    
        public override string ToString()
        {
            return CostUnitId + " " + Name + " " + Type;
        }
    }
    
    public class StockUnit
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }
    
    public class MachineUnit
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }
    
    public class MaintenanceUnit
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }
    

    The ExpressionTransformer class

    Most of the time, using the Mapper static class is fine, but sometimes you need to map the same types with different configurations, so you need to explicitly use an IMappingEngine like this:

    var configuration = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
    var engine = new MappingEngine(configuration);
    

    The way to create a MappingEngine is not obvious at all. I had to dig in the source code to find how it was done.

    public static class ExpressionTransformer
    {
        private static readonly MappingEngine Mapper;
    
        /// 
        /// Initializes the  class.
        /// Creates an instance of AutoMapper. Initializes mappings.
        /// 
        static ExpressionTransformer()
        {
            ConfigurationStore configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
    
            // Create mapping
            // Maps Id from StockUnit to CostUnitId from CostUnit
            configurationStore.CreateMap()
                .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
            // Maps Id from MachineUnit to CostUnitId from CostUnit
            configurationStore.CreateMap()
                .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
            // Maps Id from MaintenanceUnit to CostUnitId from CostUnit
            configurationStore.CreateMap()
                .ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
    
            // Create instance of AutoMapper
            Mapper = new MappingEngine(configurationStore);
        }
    
        public static Expression> Tranform(Expression> sourceExpression)
        {
            // Resolve mappings by AutoMapper for given types.
            var map = Mapper.ConfigurationProvider.FindTypeMapFor(typeof(TSource), typeof(TDestination));
    
            if (map == null)
            {
                throw new AutoMapperMappingException(string.Format("No Mapping found for {0} --> {1}.", typeof(TSource).Name, typeof(TDestination).Name));
            }
    
            // Transform from TSource to TDestination with specified mappings
            var visitor = new ParameterTypeVisitor(sourceExpression, map.GetPropertyMaps());
            var expression = visitor.Transform();
    
            return expression;
        }
    
        private class ParameterTypeVisitor : ExpressionVisitor
        {
            private readonly Dictionary _parameters;
            private readonly Expression> _expression;
            private readonly IEnumerable _maps;
    
            public ParameterTypeVisitor(Expression> expression, IEnumerable maps)
            {
                _parameters = expression.Parameters
                    .ToDictionary(p => p.Name, p => Expression.Parameter(typeof(TDestination), p.Name));
    
                _expression = expression;
    
                _maps = maps;
            }
    
            public Expression> Transform()
            {
                return (Expression>) Visit(_expression);
            }
    
            protected override Expression VisitMember(MemberExpression node)
            {
                if (node.Member.DeclaringType == typeof(TSource))
                {
                    var memberName = node.Member.Name;
                    var member = _maps.FirstOrDefault(p => typeof(TSource) == node.Expression.Type
                                                            && p.SourceMember.Name == memberName);
                    if (member != null)
                    {
                        // Return Property from TDestination
                        var expression = Visit(node.Expression);
                        return Expression.MakeMemberAccess(expression, member.DestinationProperty.MemberInfo);
                    }
                }
    
                return base.VisitMember(node);
            }
    
            protected override Expression VisitParameter(ParameterExpression node)
            {
                var parameter = _parameters[node.Name];
                return parameter;
            }
    
            protected override Expression VisitLambda(Expression node)
            {
                var expression = Visit(node.Body);
                return Expression.Lambda(expression, _parameters.Select(x => x.Value));
            }
        }
    }
    

    Usage

    To Convert an expression we just need to call Transform Method of ExpressionTransformer class

                Expression> stockQuery = unit => unit.Id == "0815" && unit.Name == "ABC";
    
                // Call Transform method.
                Expression> costUnitQuery = ExpressionTransformer.Tranform(stockQuery);
    

    Result

提交回复
热议问题