Skip to content

Commit e16014d

Browse files
committed
Supporting AutoMapper v10. Fixing Issue#79 (Local variable in expression not mapped using MapExpression).
1 parent 594d8e8 commit e16014d

File tree

13 files changed

+251
-126
lines changed

13 files changed

+251
-126
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Authors>Jimmy Bogard</Authors>
44
<LangVersion>latest</LangVersion>
5-
<VersionPrefix>3.1.2</VersionPrefix>
5+
<VersionPrefix>4.0.0-preview01</VersionPrefix>
66
<WarningsAsErrors>true</WarningsAsErrors>
77
<NoWarn>$(NoWarn);1701;1702;1591</NoWarn>
88
</PropertyGroup>

src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</PropertyGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="AutoMapper" Version="9.0.0" />
20+
<PackageReference Include="AutoMapper" Version="[10.0.0,11.0.0)" />
2121
</ItemGroup>
2222

2323
</Project>

src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
using System.Linq.Expressions;
55
using System.Reflection;
66
using AutoMapper.Extensions.ExpressionMapping;
7+
using AutoMapper.Internal;
78
using static System.Linq.Expressions.Expression;
89

910
namespace AutoMapper.Mappers
1011
{
1112
public class ExpressionMapper : IObjectMapper
1213
{
13-
private static TDestination Map<TSource, TDestination>(TSource expression, ResolutionContext context)
14+
private static TDestination Map<TSource, TDestination>(TSource expression, ResolutionContext context, IConfigurationProvider configurationProvider)
1415
where TSource : LambdaExpression
15-
where TDestination : LambdaExpression => context.Mapper.MapExpression<TDestination>(expression);
16+
where TDestination : LambdaExpression => configurationProvider.CreateMapper().MapExpression<TDestination>(expression);
1617

1718
private static readonly MethodInfo MapMethodInfo = typeof(ExpressionMapper).GetDeclaredMethod(nameof(Map));
1819

@@ -25,7 +26,8 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr
2526
Call(null,
2627
MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpression.Type),
2728
sourceExpression,
28-
contextExpression);
29+
contextExpression,
30+
Constant(configurationProvider));
2931

3032
internal class MappingVisitor : ExpressionVisitor
3133
{

src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ public static Expression ConvertTypeIfNecessary(this Expression expression, Type
9292
return expression;
9393
}
9494

95+
public static MemberExpression GetMemberExpression(this LambdaExpression expr)
96+
=> expr.Body.GetUnconvertedMemberExpression() as MemberExpression;
97+
9598
/// <summary>
9699
/// Returns the ParameterExpression for the LINQ parameter.
97100
/// </summary>
@@ -140,10 +143,15 @@ public static ParameterExpression GetParameterExpression(this Expression express
140143
/// </summary>
141144
/// <param name="expression"></param>
142145
/// <returns></returns>
143-
public static Expression GetBaseOfMemberExpression(this MemberExpression expression)
144-
=> expression.Expression.NodeType == ExpressionType.MemberAccess
145-
? GetBaseOfMemberExpression((MemberExpression)expression.Expression)
146-
: expression.Expression;
146+
public static Expression GetBaseOfMemberExpression(this MemberExpression expression)
147+
{
148+
if (expression.Expression == null)
149+
return null;
150+
151+
return expression.Expression.NodeType == ExpressionType.MemberAccess
152+
? GetBaseOfMemberExpression((MemberExpression)expression.Expression)
153+
: expression.Expression;
154+
}
147155

148156
/// <summary>
149157
/// Adds member expressions to an existing expression.

src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1+
using AutoMapper.Internal;
2+
using AutoMapper.Mappers;
13
using System;
24
using System.Collections;
35
using System.Collections.Generic;
46
using System.Linq;
57
using System.Linq.Expressions;
68
using System.Reflection;
79
using System.Runtime.CompilerServices;
8-
using AutoMapper.Mappers;
9-
using AutoMapper.Mappers.Internal;
10-
using AutoMapper.QueryableExtensions;
1110

1211
namespace AutoMapper.Extensions.ExpressionMapping.Impl
1312
{
13+
using static Expression;
1414
using MemberPaths = IEnumerable<IEnumerable<MemberInfo>>;
1515
using ParameterBag = IDictionary<string, object>;
16-
using static Expression;
1716

1817
public class SourceInjectedQueryProvider<TSource, TDestination> : IQueryProvider
1918
{

src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
using System;
1+
using AutoMapper.Internal;
2+
using System;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Linq.Expressions;
5-
using static System.Linq.Expressions.Expression;
66
using System.Reflection;
7-
using AutoMapper.Mappers.Internal;
8-
using AutoMapper.Internal;
7+
using static System.Linq.Expressions.Expression;
98

109
namespace AutoMapper.Extensions.ExpressionMapping
1110
{
@@ -36,10 +35,22 @@ private static TDestDelegate _MapExpression<TSourceDelegate, TDestDelegate>(this
3635
where TDestDelegate : LambdaExpression
3736
=> mapper.MapExpression<TSourceDelegate, TDestDelegate>(expression);
3837

39-
4038
private static MethodInfo GetMapExpressionMethod(this string methodName)
4139
=> typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
4240

41+
public static object MapObject(this IMapper mapper, object obj, Type sourceType, Type destType)
42+
=> "_MapObject".GetMapObjectMethod().MakeGenericMethod
43+
(
44+
sourceType,
45+
destType
46+
).Invoke(null, new object[] { mapper, obj });
47+
48+
private static TDest _MapObject<TSource, TDest>(IMapper mapper, TSource source)
49+
=> mapper.Map<TSource, TDest>(source);
50+
51+
private static MethodInfo GetMapObjectMethod(this string methodName)
52+
=> typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
53+
4354
/// <summary>
4455
/// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value.
4556
/// </summary>

src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ public static object GetDefaultValue(this ParameterInfo parameter)
1717
public static object MapMember(this ResolutionContext context, MemberInfo member, object value, object destination = null)
1818
=> ReflectionHelper.MapMember(context, member, value, destination);
1919

20-
public static bool IsDynamic(this object obj)
21-
=> ReflectionHelper.IsDynamic(obj);
22-
23-
public static bool IsDynamic(this Type type)
24-
=> ReflectionHelper.IsDynamic(type);
25-
2620
public static void SetMemberValue(this MemberInfo propertyOrField, object target, object value)
2721
=> ReflectionHelper.SetMemberValue(propertyOrField, target, value);
2822

@@ -38,27 +32,12 @@ public static MemberPaths GetMemberPaths(Type type, string[] membersToExpand) =>
3832
public static MemberPaths GetMemberPaths<TResult>(Expression<Func<TResult, object>>[] membersToExpand) =>
3933
membersToExpand.Select(expr => MemberVisitor.GetMemberPath(expr));
4034

41-
public static MemberInfo GetFieldOrProperty(this LambdaExpression expression)
42-
=> ReflectionHelper.GetFieldOrProperty(expression);
43-
4435
public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
4536
=> ReflectionHelper.FindProperty(lambdaExpression);
4637

4738
public static Type GetMemberType(this MemberInfo memberInfo)
4839
=> ReflectionHelper.GetMemberType(memberInfo);
4940

50-
/// <summary>
51-
/// if targetType is oldType, method will return newType
52-
/// if targetType is not oldType, method will return targetType
53-
/// if targetType is generic type with oldType arguments, method will replace all oldType arguments on newType
54-
/// </summary>
55-
/// <param name="targetType"></param>
56-
/// <param name="oldType"></param>
57-
/// <param name="newType"></param>
58-
/// <returns></returns>
59-
public static Type ReplaceItemType(this Type targetType, Type oldType, Type newType)
60-
=> ReflectionHelper.ReplaceItemType(targetType, oldType, newType);
61-
6241
public static IEnumerable<TypeInfo> GetDefinedTypes(this Assembly assembly) =>
6342
assembly.DefinedTypes;
6443

src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using AutoMapper.Configuration.Internal;
1+
using AutoMapper.Internal;
22
using System;
33
using System.Collections.Generic;
44
using System.Linq;
@@ -124,7 +124,7 @@ public static bool IsNotPublic(this ConstructorInfo constructorInfo) => construc
124124

125125
public static bool IsLiteralType(this Type type)
126126
{
127-
if (PrimitiveHelper.IsNullableType(type))
127+
if (type.IsNullableType())
128128
type = Nullable.GetUnderlyingType(type);
129129

130130
return LiteralTypes.Contains(type);
@@ -165,5 +165,11 @@ public static bool IsLiteralType(this Type type)
165165
public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) => propertyInfo.SetMethod;
166166

167167
public static FieldInfo GetField(this Type type, string name) => type.GetRuntimeField(name);
168+
169+
public static bool IsQueryableType(this Type type)
170+
=> typeof(IQueryable).IsAssignableFrom(type);
171+
172+
public static Type GetGenericElementType(this Type type)
173+
=> type.HasElementType ? type.GetElementType() : type.GetTypeInfo().GenericTypeArguments[0];
168174
}
169175
}

src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,40 @@ protected override Expression VisitParameter(ParameterExpression node)
3838
return !pair.Equals(default(KeyValuePair<Type, MapperInfo>)) ? pair.Value.NewParameter : base.VisitParameter(node);
3939
}
4040

41+
private object GetConstantValue(object constantObject, string fullName, Type parentType)
42+
{
43+
return fullName.Split('.').Aggregate(constantObject, (parent, memberName) =>
44+
{
45+
MemberInfo memberInfo = parentType.GetFieldOrProperty(memberName);
46+
parentType = memberInfo.GetMemberType();
47+
return memberInfo.GetMemberValue(parent);
48+
});
49+
}
50+
4151
protected override Expression VisitMember(MemberExpression node)
4252
{
4353
var parameterExpression = node.GetParameterExpression();
4454
if (parameterExpression == null)
55+
{
56+
var baseExpression = node.GetBaseOfMemberExpression();
57+
if (baseExpression?.NodeType == ExpressionType.Constant)
58+
{
59+
return this.Visit
60+
(
61+
Expression.Constant
62+
(
63+
GetConstantValue
64+
(
65+
((ConstantExpression)baseExpression).Value,
66+
node.GetPropertyFullName(),
67+
baseExpression.Type
68+
)
69+
)
70+
);
71+
}
72+
4573
return base.VisitMember(node);
74+
}
4675

4776
InfoDictionary.Add(parameterExpression, TypeMappings);
4877
return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), new List<PropertyMapInfo>());
@@ -145,33 +174,58 @@ protected override Expression VisitMemberInit(MemberInitExpression node)
145174
//The destination becomes the source because to map a source expression to a destination expression,
146175
//we need the expressions used to create the source from the destination
147176

148-
IEnumerable<MemberBinding> bindings = node.Bindings.Select
149-
(
150-
binding =>
151-
{
152-
Expression bindingExpression = ((MemberAssignment)binding).Expression;
153-
return DoBind
177+
//IEnumerable<MemberBinding> bindings = node.Bindings.Select
178+
//(
179+
// binding =>
180+
// {
181+
// Expression bindingExpression = ((MemberAssignment)binding).Expression;
182+
// return DoBind
183+
// (
184+
// GetSourceMember(typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name)),
185+
// bindingExpression,
186+
// this.Visit(bindingExpression)
187+
// );
188+
// }
189+
//);
190+
191+
IEnumerable<MemberBinding> bindings = node.Bindings.Aggregate(new List<MemberBinding>(), (list, binding) =>
192+
{
193+
var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == binding.Member.Name);
194+
if (propertyMap == null)
195+
return list;
196+
197+
Expression bindingExpression = ((MemberAssignment)binding).Expression;
198+
list.Add
199+
(
200+
DoBind
154201
(
155-
typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name),
202+
GetSourceMember(propertyMap),
156203
bindingExpression,
157204
this.Visit(bindingExpression)
158-
);
159-
}
160-
);
205+
)
206+
);
207+
208+
return list;
209+
});
161210

162211
return Expression.MemberInit(Expression.New(newType), bindings);
163212
}
164213

165214
return base.VisitMemberInit(node);
166215
}
167216

168-
private MemberBinding DoBind(PropertyMap propertyMap, Expression initial, Expression mapped)
217+
private MemberBinding DoBind(MemberInfo sourceMember, Expression initial, Expression mapped)
169218
{
170-
mapped = mapped.ConvertTypeIfNecessary(propertyMap.SourceMember.GetMemberType());
219+
mapped = mapped.ConvertTypeIfNecessary(sourceMember.GetMemberType());
171220
this.TypeMappings.AddTypeMapping(ConfigurationProvider, initial.Type, mapped.Type);
172-
return Expression.Bind(propertyMap.SourceMember, mapped);
221+
return Expression.Bind(sourceMember, mapped);
173222
}
174223

224+
private MemberInfo GetSourceMember(PropertyMap propertyMap)
225+
=> propertyMap.CustomMapExpression != null
226+
? propertyMap.CustomMapExpression.GetMemberExpression()?.Member
227+
: propertyMap.SourceMember;
228+
175229
protected override Expression VisitBinary(BinaryExpression node)
176230
{
177231
return DoVisitBinary(this.Visit(node.Left), this.Visit(node.Right), this.Visit(node.Conversion));
@@ -261,7 +315,9 @@ Expression DoVisitUnary(Expression updated)
261315
protected override Expression VisitConstant(ConstantExpression node)
262316
{
263317
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
264-
return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType));
318+
return base.VisitConstant(Expression.Constant(Mapper.MapObject(node.Value, node.Type, newType), newType));
319+
//Issue 3455 (Non-Generic Mapper.Map failing for structs in v10)
320+
//return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType));
265321

266322
return base.VisitConstant(node);
267323
}

0 commit comments

Comments
 (0)