tools/AutoMapper/XpressionMapper/XpressionMapperVisitor.cs (225 lines of code) (raw):

using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using AutoMapper.Configuration; using AutoMapper.Internal; using AutoMapper.QueryableExtensions.Impl; using AutoMapper.XpressionMapper.ArgumentMappers; using AutoMapper.XpressionMapper.Extensions; using AutoMapper.XpressionMapper.Structures; namespace AutoMapper.XpressionMapper { public class XpressionMapperVisitor : ExpressionVisitor { public XpressionMapperVisitor(IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings) { TypeMappings = typeMappings; InfoDictionary = new MapperInfoDictionary(new ParameterExpressionEqualityComparer()); ConfigurationProvider = configurationProvider; } public MapperInfoDictionary InfoDictionary { get; } public Dictionary<Type, Type> TypeMappings { get; } protected IConfigurationProvider ConfigurationProvider { get; } protected override Expression VisitParameter(ParameterExpression parameterExpression) { InfoDictionary.Add(parameterExpression, TypeMappings); var pair = InfoDictionary.SingleOrDefault(a => a.Key.Equals(parameterExpression)); return !pair.Equals(default(KeyValuePair<Type, MapperInfo>)) ? pair.Value.NewParameter : base.VisitParameter(parameterExpression); } protected override Expression VisitMember(MemberExpression node) { string sourcePath; var parameterExpression = node.GetParameterExpression(); if (parameterExpression == null) return base.VisitMember(node); InfoDictionary.Add(parameterExpression, TypeMappings); var sType = parameterExpression.Type; if (InfoDictionary.ContainsKey(parameterExpression) && node.IsMemberExpression()) { sourcePath = node.GetPropertyFullName(); } else { return base.VisitMember(node); } var propertyMapInfoList = new List<PropertyMapInfo>(); FindDestinationFullName(sType, InfoDictionary[parameterExpression].DestType, sourcePath, propertyMapInfoList); string fullName; if (propertyMapInfoList.Any(x => x.CustomExpression != null)) { var last = propertyMapInfoList.Last(x => x.CustomExpression != null); var beforeCustExpression = propertyMapInfoList.Aggregate(new List<PropertyMapInfo>(), (list, next) => { if (propertyMapInfoList.IndexOf(next) < propertyMapInfoList.IndexOf(last)) list.Add(next); return list; }); var afterCustExpression = propertyMapInfoList.Aggregate(new List<PropertyMapInfo>(), (list, next) => { if (propertyMapInfoList.IndexOf(next) > propertyMapInfoList.IndexOf(last)) list.Add(next); return list; }); fullName = BuildFullName(beforeCustExpression); var visitor = new PrependParentNameVisitor(last.CustomExpression.Parameters[0].Type/*Parent type of current property*/, fullName, InfoDictionary[parameterExpression].NewParameter); var ex = propertyMapInfoList[propertyMapInfoList.Count - 1] != last ? visitor.Visit(last.CustomExpression.Body.MemberAccesses(afterCustExpression)) : visitor.Visit(last.CustomExpression.Body); this.TypeMappings.AddTypeMapping(node.Type, ex.Type); return ex; } fullName = BuildFullName(propertyMapInfoList); var me = ExpressionFactory.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter); this.TypeMappings.AddTypeMapping(node.Type, me.Type); return me; } protected override Expression VisitConstant(ConstantExpression node) { if (this.TypeMappings.TryGetValue(node.Type, out Type newType)) return base.VisitConstant(Expression.Constant(node.Value, newType)); return base.VisitConstant(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { var parameterExpression = node.GetParameterExpression(); if (parameterExpression == null) return base.VisitMethodCall(node); InfoDictionary.Add(parameterExpression, TypeMappings); var listOfArgumentsForNewMethod = node.Arguments.Aggregate(new List<Expression>(), (lst, next) => { var mappedNext = ArgumentMapper.Create(this, next).MappedArgumentExpression; TypeMappings.AddTypeMapping(next.Type, mappedNext.Type); lst.Add(mappedNext); return lst; });//Arguments could be expressions or other objects. e.g. s => s.UserId or a string "ZZZ". For extention methods node.Arguments[0] is usually the helper object itself //type args are the generic type args e.g. T1 and T2 MethodName<T1, T2>(method arguments); var typeArgsForNewMethod = node.Method.IsGenericMethod ? node.Method.GetGenericArguments().Select(i => TypeMappings.ContainsKey(i) ? TypeMappings[i] : i).ToList()//not converting the type it is not in the typeMappings dictionary : null; MethodCallExpression resultExp; if (!node.Method.IsStatic) { var instance = ArgumentMapper.Create(this, node.Object).MappedArgumentExpression; resultExp = node.Method.IsGenericMethod ? Expression.Call(instance, node.Method.Name, typeArgsForNewMethod.ToArray(), listOfArgumentsForNewMethod.ToArray()) : Expression.Call(instance, node.Method, listOfArgumentsForNewMethod.ToArray()); } else { resultExp = node.Method.IsGenericMethod ? Expression.Call(node.Method.DeclaringType, node.Method.Name, typeArgsForNewMethod.ToArray(), listOfArgumentsForNewMethod.ToArray()) : Expression.Call(node.Method, listOfArgumentsForNewMethod.ToArray()); } return resultExp; } protected string BuildFullName(List<PropertyMapInfo> propertyMapInfoList) { var fullName = string.Empty; foreach (var info in propertyMapInfoList) { if (info.CustomExpression != null) { fullName = string.IsNullOrEmpty(fullName) ? info.CustomExpression.GetMemberFullName() : string.Concat(fullName, ".", info.CustomExpression.GetMemberFullName()); } else { var additions = info.DestinationPropertyInfos.Aggregate(new StringBuilder(fullName), (sb, next) => { if (sb.ToString() == string.Empty) sb.Append(next.Name); else { sb.Append("."); sb.Append(next.Name); } return sb; }); fullName = additions.ToString(); } } return fullName; } private static void AddPropertyMapInfo(Type parentType, string name, List<PropertyMapInfo> propertyMapInfoList) { var sourceMemberInfo = parentType.GetFieldOrProperty(name); switch (sourceMemberInfo) { case PropertyInfo propertyInfo: propertyMapInfoList.Add(new PropertyMapInfo(null, new List<MemberInfo> {propertyInfo})); break; case FieldInfo fieldInfo: propertyMapInfoList.Add(new PropertyMapInfo(null, new List<MemberInfo> {fieldInfo})); break; } } protected void FindDestinationFullName(Type typeSource, Type typeDestination, string sourceFullName, List<PropertyMapInfo> propertyMapInfoList) { const string period = "."; if (typeSource == typeDestination) { var sourceFullNameArray = sourceFullName.Split(new[] { period[0] }, StringSplitOptions.RemoveEmptyEntries); sourceFullNameArray.Aggregate(propertyMapInfoList, (list, next) => { if (list.Count == 0) { AddPropertyMapInfo(typeSource, next, list); } else { var last = list[list.Count - 1]; AddPropertyMapInfo(last.CustomExpression == null ? last.DestinationPropertyInfos[last.DestinationPropertyInfos.Count - 1].GetMemberType() : last.CustomExpression.ReturnType, next, list); } return list; }); return; } var typeMap = ConfigurationProvider.CheckIfMapExists(sourceType: typeDestination, destinationType: typeSource);//The destination becomes the source because to map a source expression to a destination expression, //we need the expressions used to create the source from the destination PathMap pathMap = typeMap.FindPathMapByDestinationPath(destinationFullPath: sourceFullName); if (pathMap != null) { propertyMapInfoList.Add(new PropertyMapInfo(pathMap.SourceExpression, new List<MemberInfo>())); return; } if (sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) < 0) { var propertyMap = typeMap.GetPropertyMapByDestinationProperty(sourceFullName); var sourceMemberInfo = typeSource.GetFieldOrProperty(propertyMap.DestinationProperty.Name); if (propertyMap.ValueResolverConfig != null) { throw new InvalidOperationException(Resource.customResolversNotSupported); } if (propertyMap.CustomExpression != null) { if (propertyMap.CustomExpression.ReturnType.IsValueType() && sourceMemberInfo.GetMemberType() != propertyMap.CustomExpression.ReturnType) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.expressionMapValueTypeMustMatchFormat, propertyMap.CustomExpression.ReturnType.Name, propertyMap.CustomExpression.ToString(), sourceMemberInfo.GetMemberType().Name, propertyMap.DestinationProperty.Name)); } else { if (propertyMap.SourceMember.GetMemberType().IsValueType() && sourceMemberInfo.GetMemberType() != propertyMap.SourceMember.GetMemberType()) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.expressionMapValueTypeMustMatchFormat, propertyMap.SourceMember.GetMemberType().Name, propertyMap.SourceMember.Name, sourceMemberInfo.GetMemberType().Name, propertyMap.DestinationProperty.Name)); } propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomExpression, propertyMap.SourceMembers.ToList())); } else { var propertyName = sourceFullName.Substring(0, sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase)); var propertyMap = typeMap.GetPropertyMapByDestinationProperty(propertyName); var sourceMemberInfo = typeSource.GetFieldOrProperty(propertyMap.DestinationProperty.Name); if (propertyMap.CustomExpression == null && propertyMap.SourceMember == null)//If sourceFullName has a period then the SourceMember cannot be null. The SourceMember is required to find the ProertyMap of its child object. throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.srcMemberCannotBeNullFormat, typeSource.Name, typeDestination.Name, propertyName)); propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomExpression, propertyMap.SourceMembers.ToList())); var childFullName = sourceFullName.Substring(sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) + 1); FindDestinationFullName(sourceMemberInfo.GetMemberType(), propertyMap.CustomExpression == null ? propertyMap.SourceMember.GetMemberType() : propertyMap.CustomExpression.ReturnType, childFullName, propertyMapInfoList); } } } }