Skip to content

Commit 2c941cf

Browse files
committed
1. Improves mapping of constansts in expressions.
2. Maps expressions where the type changes for instance methods. 3. Fixes #2543 (XpressionMapper does not convert the delegate return type).
1 parent 77f1820 commit 2c941cf

File tree

6 files changed

+352
-93
lines changed

6 files changed

+352
-93
lines changed

src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ namespace AutoMapper.Extensions.ExpressionMapping
1111
{
1212
public class MapIncludesVisitor : XpressionMapperVisitor
1313
{
14-
public MapIncludesVisitor(IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings)
15-
: base(configurationProvider, typeMappings)
14+
public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings)
15+
: base(mapper, configurationProvider, typeMappings)
1616
{
1717
}
1818

src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs

Lines changed: 126 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using static System.Linq.Expressions.Expression;
66
using System.Reflection;
77
using AutoMapper.Mappers.Internal;
8+
using AutoMapper.Internal;
89

910
namespace AutoMapper.Extensions.ExpressionMapping
1011
{
@@ -21,22 +22,60 @@ public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, La
2122
where TDestDelegate : LambdaExpression
2223
{
2324
if (expression == null)
24-
return default(TDestDelegate);
25+
return default;
2526

26-
var typeSourceFunc = expression.GetType().GetGenericArguments()[0];
27-
var typeDestFunc = typeof(TDestDelegate).GetGenericArguments()[0];
27+
return mapper.MapExpression<TDestDelegate>
28+
(
29+
expression,
30+
(config, mappings) => new XpressionMapperVisitor(mapper, config, mappings)
31+
);
32+
}
33+
34+
private static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression, Func<IConfigurationProvider, Dictionary<Type, Type>, XpressionMapperVisitor> getVisitor)
35+
where TDestDelegate : LambdaExpression
36+
{
37+
return MapExpression<TDestDelegate>
38+
(
39+
mapper ?? Mapper.Instance,
40+
mapper == null ? Mapper.Configuration : mapper.ConfigurationProvider,
41+
expression,
42+
expression.GetType().GetGenericArguments()[0],
43+
typeof(TDestDelegate).GetGenericArguments()[0],
44+
getVisitor
45+
);
46+
}
2847

29-
var typeMappings = new Dictionary<Type, Type>()
30-
.AddTypeMappingsFromDelegates(typeSourceFunc, typeDestFunc);
48+
private static TDestDelegate MapExpression<TDestDelegate>(IMapper mapper,
49+
IConfigurationProvider configurationProvider,
50+
LambdaExpression expression,
51+
Type typeSourceFunc,
52+
Type typeDestFunc,
53+
Func<IConfigurationProvider, Dictionary<Type, Type>, XpressionMapperVisitor> getVisitor)
54+
where TDestDelegate : LambdaExpression
55+
{
56+
return CreateVisitor(new Dictionary<Type, Type>().AddTypeMappingsFromDelegates(configurationProvider, typeSourceFunc, typeDestFunc));
57+
58+
TDestDelegate CreateVisitor(Dictionary<Type, Type> typeMappings)
59+
=> MapBody(typeMappings, getVisitor(configurationProvider, typeMappings));
3160

32-
var visitor = new XpressionMapperVisitor(mapper == null ? Mapper.Configuration : mapper.ConfigurationProvider, typeMappings);
33-
var remappedBody = visitor.Visit(expression.Body);
34-
if (remappedBody == null)
35-
throw new InvalidOperationException(Resource.cantRemapExpression);
61+
TDestDelegate MapBody(Dictionary<Type, Type> typeMappings, XpressionMapperVisitor visitor)
62+
=> GetLambda(typeMappings, visitor, visitor.Visit(expression.Body));
3663

37-
return (TDestDelegate)Lambda(typeDestFunc, remappedBody, expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings));
64+
TDestDelegate GetLambda(Dictionary<Type, Type> typeMappings, XpressionMapperVisitor visitor, Expression remappedBody)
65+
{
66+
if (remappedBody == null)
67+
throw new InvalidOperationException(Resource.cantRemapExpression);
68+
69+
return (TDestDelegate)Lambda
70+
(
71+
typeDestFunc,
72+
ExpressionFactory.ToType(remappedBody, typeDestFunc.GetGenericArguments().Last()),
73+
expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings)
74+
);
75+
}
3876
}
3977

78+
4079
/// <summary>
4180
/// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value.
4281
/// </summary>
@@ -47,7 +86,7 @@ public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, La
4786
/// <returns></returns>
4887
public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
4988
where TSourceDelegate : LambdaExpression
50-
where TDestDelegate : LambdaExpression
89+
where TDestDelegate : LambdaExpression
5190
=> mapper.MapExpression<TDestDelegate>(expression);
5291

5392
/// <summary>
@@ -61,20 +100,13 @@ public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper m
61100
where TDestDelegate : LambdaExpression
62101
{
63102
if (expression == null)
64-
return null;
65-
66-
var typeSourceFunc = expression.GetType().GetGenericArguments()[0];
67-
var typeDestFunc = typeof(TDestDelegate).GetGenericArguments()[0];
68-
69-
var typeMappings = new Dictionary<Type, Type>()
70-
.AddTypeMappingsFromDelegates(typeSourceFunc, typeDestFunc);
71-
72-
XpressionMapperVisitor visitor = new MapIncludesVisitor(mapper == null ? Mapper.Configuration : mapper.ConfigurationProvider, typeMappings);
73-
var remappedBody = visitor.Visit(expression.Body);
74-
if (remappedBody == null)
75-
throw new InvalidOperationException(Resource.cantRemapExpression);
103+
return default;
76104

77-
return (TDestDelegate)Lambda(typeDestFunc, remappedBody, expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings));
105+
return mapper.MapExpression<TDestDelegate>
106+
(
107+
expression,
108+
(config, mappings) => new MapIncludesVisitor(mapper, config, mappings)
109+
);
78110
}
79111

80112
/// <summary>
@@ -87,7 +119,7 @@ public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper m
87119
/// <returns></returns>
88120
public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
89121
where TSourceDelegate : LambdaExpression
90-
where TDestDelegate : LambdaExpression
122+
where TDestDelegate : LambdaExpression
91123
=> mapper.MapExpressionAsInclude<TDestDelegate>(expression);
92124

93125
/// <summary>
@@ -100,7 +132,7 @@ public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegat
100132
/// <returns></returns>
101133
public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
102134
where TSourceDelegate : LambdaExpression
103-
where TDestDelegate : LambdaExpression
135+
where TDestDelegate : LambdaExpression
104136
=> collection?.Select(mapper.MapExpression<TSourceDelegate, TDestDelegate>).ToList();
105137

106138
/// <summary>
@@ -111,7 +143,7 @@ public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDes
111143
/// <param name="collection"></param>
112144
/// <returns></returns>
113145
public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
114-
where TDestDelegate : LambdaExpression
146+
where TDestDelegate : LambdaExpression
115147
=> collection?.Select(mapper.MapExpression<TDestDelegate>).ToList();
116148

117149
/// <summary>
@@ -124,7 +156,7 @@ public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this I
124156
/// <returns></returns>
125157
public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
126158
where TSourceDelegate : LambdaExpression
127-
where TDestDelegate : LambdaExpression
159+
where TDestDelegate : LambdaExpression
128160
=> collection?.Select(mapper.MapExpressionAsInclude<TSourceDelegate, TDestDelegate>).ToList();
129161

130162
/// <summary>
@@ -135,7 +167,7 @@ public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestD
135167
/// <param name="collection"></param>
136168
/// <returns></returns>
137169
public static ICollection<TDestDelegate> MapIncludesList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
138-
where TDestDelegate : LambdaExpression
170+
where TDestDelegate : LambdaExpression
139171
=> collection?.Select(mapper.MapExpressionAsInclude<TDestDelegate>).ToList();
140172

141173
/// <summary>
@@ -161,47 +193,37 @@ public static List<ParameterExpression> GetDestinationParameterExpressions(this
161193
/// <typeparam name="TSource"></typeparam>
162194
/// <typeparam name="TDest"></typeparam>
163195
/// <param name="typeMappings"></param>
196+
/// <param name="configurationProvider"></param>
164197
/// <returns></returns>
165-
public static Dictionary<Type, Type> AddTypeMapping<TSource, TDest>(this Dictionary<Type, Type> typeMappings)
198+
public static Dictionary<Type, Type> AddTypeMapping<TSource, TDest>(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider)
166199
=> typeMappings == null
167200
? throw new ArgumentException(Resource.typeMappingsDictionaryIsNull)
168-
: typeMappings.AddTypeMapping(typeof(TSource), typeof(TDest));
201+
: typeMappings.AddTypeMapping(configurationProvider, typeof(TSource), typeof(TDest));
169202

170203
private static bool HasUnderlyingType(this Type type)
171204
{
172205
return (type.IsGenericType() && typeof(System.Collections.IEnumerable).IsAssignableFrom(type)) || type.IsArray;
173206
}
174207

175-
private static void AddUnderlyingTypes(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
208+
private static void AddUnderlyingTypes(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
176209
{
177-
var sourceArguments = !sourceType.HasUnderlyingType()
178-
? new List<Type>()
179-
: ElementTypeHelper.GetElementTypes(sourceType).ToList();
180-
181-
var destArguments = !destType.HasUnderlyingType()
182-
? new List<Type>()
183-
: ElementTypeHelper.GetElementTypes(destType).ToList();
184-
185-
if (sourceArguments.Count != destArguments.Count)
186-
throw new ArgumentException(Resource.invalidArgumentCount);
187-
188-
sourceArguments.Aggregate(typeMappings, (dic, next) =>
189-
{
190-
if (!dic.ContainsKey(next) && next != destArguments[sourceArguments.IndexOf(next)])
191-
dic.AddTypeMapping(next, destArguments[sourceArguments.IndexOf(next)]);
192-
193-
return dic;
194-
});
210+
typeMappings.DoAddTypeMappings
211+
(
212+
configurationProvider,
213+
!sourceType.HasUnderlyingType() ? new List<Type>() : ElementTypeHelper.GetElementTypes(sourceType).ToList(),
214+
!destType.HasUnderlyingType() ? new List<Type>() : ElementTypeHelper.GetElementTypes(destType).ToList()
215+
);
195216
}
196217

197218
/// <summary>
198219
/// Adds a new source and destination key-value pair to a dictionary of type mappings based on the arguments.
199220
/// </summary>
200221
/// <param name="typeMappings"></param>
222+
/// <param name="configurationProvider"></param>
201223
/// <param name="sourceType"></param>
202224
/// <param name="destType"></param>
203225
/// <returns></returns>
204-
public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
226+
public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
205227
{
206228
if (typeMappings == null)
207229
throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
@@ -216,32 +238,75 @@ public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type>
216238
{
217239
typeMappings.Add(sourceType, destType);
218240
if (typeof(Delegate).IsAssignableFrom(sourceType))
219-
typeMappings.AddTypeMappingsFromDelegates(sourceType, destType);
241+
typeMappings.AddTypeMappingsFromDelegates(configurationProvider, sourceType, destType);
220242
else
221-
typeMappings.AddUnderlyingTypes(sourceType, destType);
243+
{
244+
typeMappings.AddUnderlyingTypes(configurationProvider, sourceType, destType);
245+
typeMappings.FindChildPropertyTypeMaps(configurationProvider, sourceType, destType);
246+
}
222247
}
223248

224249
return typeMappings;
225250
}
226251

227-
private static Dictionary<Type, Type> AddTypeMappingsFromDelegates(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
252+
private static Dictionary<Type, Type> AddTypeMappingsFromDelegates(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
228253
{
229254
if (typeMappings == null)
230255
throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
231256

232-
var sourceArguments = sourceType.GetGenericArguments().ToList();
233-
var destArguments = destType.GetGenericArguments().ToList();
257+
typeMappings.DoAddTypeMappings
258+
(
259+
configurationProvider,
260+
sourceType.GetGenericArguments().ToList(),
261+
destType.GetGenericArguments().ToList()
262+
);
234263

264+
return typeMappings;
265+
}
266+
267+
private static void DoAddTypeMappings(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, List<Type> sourceArguments, List<Type> destArguments)
268+
{
235269
if (sourceArguments.Count != destArguments.Count)
236270
throw new ArgumentException(Resource.invalidArgumentCount);
237271

238-
return sourceArguments.Aggregate(typeMappings, (dic, next) =>
272+
for (int i = 0; i < sourceArguments.Count; i++)
239273
{
240-
if (!dic.ContainsKey(next) && next != destArguments[sourceArguments.IndexOf(next)])
241-
dic.AddTypeMapping(next, destArguments[sourceArguments.IndexOf(next)]);
274+
if (!typeMappings.ContainsKey(sourceArguments[i]) && sourceArguments[i] != destArguments[i])
275+
typeMappings.AddTypeMapping(configurationProvider, sourceArguments[i], destArguments[i]);
276+
}
277+
}
242278

243-
return dic;
244-
});
279+
private static void FindChildPropertyTypeMaps(this Dictionary<Type, Type> typeMappings, IConfigurationProvider ConfigurationProvider, Type source, Type dest)
280+
{
281+
//The destination becomes the source because to map a source expression to a destination expression,
282+
//we need the expressions used to create the source from the destination
283+
var typeMap = ConfigurationProvider.ResolveTypeMap(sourceType: dest, destinationType: source);
284+
285+
if (typeMap == null)
286+
return;
287+
288+
FindMaps(typeMap.GetPropertyMaps().ToList());
289+
void FindMaps(List<PropertyMap> maps)
290+
{
291+
foreach (PropertyMap pm in maps)
292+
{
293+
if (pm.SourceMember == null)
294+
continue;
295+
296+
AddChildMappings
297+
(
298+
source.GetFieldOrProperty(pm.DestinationProperty.Name).GetMemberType(),
299+
pm.SourceMember.GetMemberType()
300+
);
301+
void AddChildMappings(Type sourcePropertyType, Type destPropertyType)
302+
{
303+
if (sourcePropertyType.IsLiteralType() || destPropertyType.IsLiteralType())
304+
return;
305+
306+
typeMappings.AddTypeMapping(ConfigurationProvider, sourcePropertyType, destPropertyType);
307+
}
308+
}
309+
}
245310
}
246311
}
247312
}

src/AutoMapper.Extensions.ExpressionMapping/Resource.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
<value>Can't rempa expression</value>
126126
</data>
127127
<data name="expressionMapValueTypeMustMatchFormat" xml:space="preserve">
128-
<value>The source and destination types must be the same for expression mapping between value types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}.</value>
128+
<value>The source and destination types must be the same for expression mapping between literal types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}.</value>
129129
<comment>0=Source Type; 1=SourceDescription; 2=Destination Type; 3=Destination Property.</comment>
130130
</data>
131131
<data name="includeExpressionTooComplex" xml:space="preserve">

src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using AutoMapper.Configuration.Internal;
12
using System;
23
using System.Collections.Generic;
34
using System.Linq;
@@ -126,7 +127,35 @@ public static bool IsNotPublic(this ConstructorInfo constructorInfo) => construc
126127

127128
public static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType;
128129

129-
public static bool IsLiteralType(this Type type) => type == typeof(string) || type.GetTypeInfo().IsValueType;
130+
public static bool IsLiteralType(this Type type)
131+
{
132+
if (PrimitiveHelper.IsNullableType(type))
133+
type = Nullable.GetUnderlyingType(type);
134+
135+
return LiteralTypes.Contains(type);
136+
}
137+
138+
private static HashSet<Type> LiteralTypes => new HashSet<Type>(_literalTypes);
139+
140+
private static Type[] _literalTypes => new Type[] {
141+
typeof(bool),
142+
typeof(DateTime),
143+
typeof(TimeSpan),
144+
typeof(Guid),
145+
typeof(decimal),
146+
typeof(byte),
147+
typeof(short),
148+
typeof(int),
149+
typeof(long),
150+
typeof(float),
151+
typeof(double),
152+
typeof(char),
153+
typeof(sbyte),
154+
typeof(ushort),
155+
typeof(uint),
156+
typeof(ulong),
157+
typeof(string)
158+
};
130159

131160
public static bool IsInstanceOfType(this Type type, object o) => o != null && type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
132161

0 commit comments

Comments
 (0)