Skip to content

Commit

Permalink
Supporting AutoMapper v10. Fixing Issue#79 (Local variable in express…
Browse files Browse the repository at this point in the history
…ion not mapped using MapExpression).
  • Loading branch information
BlaiseD committed Jul 4, 2020
1 parent 594d8e8 commit e16014d
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 126 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<Authors>Jimmy Bogard</Authors>
<LangVersion>latest</LangVersion>
<VersionPrefix>3.1.2</VersionPrefix>
<VersionPrefix>4.0.0-preview01</VersionPrefix>
<WarningsAsErrors>true</WarningsAsErrors>
<NoWarn>$(NoWarn);1701;1702;1591</NoWarn>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="9.0.0" />
<PackageReference Include="AutoMapper" Version="[10.0.0,11.0.0)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Extensions.ExpressionMapping;
using AutoMapper.Internal;
using static System.Linq.Expressions.Expression;

namespace AutoMapper.Mappers
{
public class ExpressionMapper : IObjectMapper
{
private static TDestination Map<TSource, TDestination>(TSource expression, ResolutionContext context)
private static TDestination Map<TSource, TDestination>(TSource expression, ResolutionContext context, IConfigurationProvider configurationProvider)
where TSource : LambdaExpression
where TDestination : LambdaExpression => context.Mapper.MapExpression<TDestination>(expression);
where TDestination : LambdaExpression => configurationProvider.CreateMapper().MapExpression<TDestination>(expression);

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

Expand All @@ -25,7 +26,8 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr
Call(null,
MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpression.Type),
sourceExpression,
contextExpression);
contextExpression,
Constant(configurationProvider));

internal class MappingVisitor : ExpressionVisitor
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public static Expression ConvertTypeIfNecessary(this Expression expression, Type
return expression;
}

public static MemberExpression GetMemberExpression(this LambdaExpression expr)
=> expr.Body.GetUnconvertedMemberExpression() as MemberExpression;

/// <summary>
/// Returns the ParameterExpression for the LINQ parameter.
/// </summary>
Expand Down Expand Up @@ -140,10 +143,15 @@ public static ParameterExpression GetParameterExpression(this Expression express
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
public static Expression GetBaseOfMemberExpression(this MemberExpression expression)
=> expression.Expression.NodeType == ExpressionType.MemberAccess
? GetBaseOfMemberExpression((MemberExpression)expression.Expression)
: expression.Expression;
public static Expression GetBaseOfMemberExpression(this MemberExpression expression)
{
if (expression.Expression == null)
return null;

return expression.Expression.NodeType == ExpressionType.MemberAccess
? GetBaseOfMemberExpression((MemberExpression)expression.Expression)
: expression.Expression;
}

/// <summary>
/// Adds member expressions to an existing expression.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using AutoMapper.Internal;
using AutoMapper.Mappers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using AutoMapper.Mappers;
using AutoMapper.Mappers.Internal;
using AutoMapper.QueryableExtensions;

namespace AutoMapper.Extensions.ExpressionMapping.Impl
{
using static Expression;
using MemberPaths = IEnumerable<IEnumerable<MemberInfo>>;
using ParameterBag = IDictionary<string, object>;
using static Expression;

public class SourceInjectedQueryProvider<TSource, TDestination> : IQueryProvider
{
Expand Down
21 changes: 16 additions & 5 deletions src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System;
using AutoMapper.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System.Reflection;
using AutoMapper.Mappers.Internal;
using AutoMapper.Internal;
using static System.Linq.Expressions.Expression;

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


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

public static object MapObject(this IMapper mapper, object obj, Type sourceType, Type destType)
=> "_MapObject".GetMapObjectMethod().MakeGenericMethod
(
sourceType,
destType
).Invoke(null, new object[] { mapper, obj });

private static TDest _MapObject<TSource, TDest>(IMapper mapper, TSource source)
=> mapper.Map<TSource, TDest>(source);

private static MethodInfo GetMapObjectMethod(this string methodName)
=> typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);

/// <summary>
/// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
Expand Down
61 changes: 0 additions & 61 deletions src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ public static object GetDefaultValue(this ParameterInfo parameter)
public static object MapMember(this ResolutionContext context, MemberInfo member, object value, object destination = null)
=> ReflectionHelper.MapMember(context, member, value, destination);

public static bool IsDynamic(this object obj)
=> ReflectionHelper.IsDynamic(obj);

public static bool IsDynamic(this Type type)
=> ReflectionHelper.IsDynamic(type);

public static void SetMemberValue(this MemberInfo propertyOrField, object target, object value)
=> ReflectionHelper.SetMemberValue(propertyOrField, target, value);

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

public static MemberInfo GetFieldOrProperty(this LambdaExpression expression)
=> ReflectionHelper.GetFieldOrProperty(expression);

public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
=> ReflectionHelper.FindProperty(lambdaExpression);

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

/// <summary>
/// if targetType is oldType, method will return newType
/// if targetType is not oldType, method will return targetType
/// if targetType is generic type with oldType arguments, method will replace all oldType arguments on newType
/// </summary>
/// <param name="targetType"></param>
/// <param name="oldType"></param>
/// <param name="newType"></param>
/// <returns></returns>
public static Type ReplaceItemType(this Type targetType, Type oldType, Type newType)
=> ReflectionHelper.ReplaceItemType(targetType, oldType, newType);

public static IEnumerable<TypeInfo> GetDefinedTypes(this Assembly assembly) =>
assembly.DefinedTypes;

Expand Down
10 changes: 8 additions & 2 deletions src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using AutoMapper.Configuration.Internal;
using AutoMapper.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -124,7 +124,7 @@ public static bool IsNotPublic(this ConstructorInfo constructorInfo) => construc

public static bool IsLiteralType(this Type type)
{
if (PrimitiveHelper.IsNullableType(type))
if (type.IsNullableType())
type = Nullable.GetUnderlyingType(type);

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

public static FieldInfo GetField(this Type type, string name) => type.GetRuntimeField(name);

public static bool IsQueryableType(this Type type)
=> typeof(IQueryable).IsAssignableFrom(type);

public static Type GetGenericElementType(this Type type)
=> type.HasElementType ? type.GetElementType() : type.GetTypeInfo().GenericTypeArguments[0];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,40 @@ protected override Expression VisitParameter(ParameterExpression node)
return !pair.Equals(default(KeyValuePair<Type, MapperInfo>)) ? pair.Value.NewParameter : base.VisitParameter(node);
}

private object GetConstantValue(object constantObject, string fullName, Type parentType)
{
return fullName.Split('.').Aggregate(constantObject, (parent, memberName) =>
{
MemberInfo memberInfo = parentType.GetFieldOrProperty(memberName);
parentType = memberInfo.GetMemberType();
return memberInfo.GetMemberValue(parent);
});
}

protected override Expression VisitMember(MemberExpression node)
{
var parameterExpression = node.GetParameterExpression();
if (parameterExpression == null)
{
var baseExpression = node.GetBaseOfMemberExpression();
if (baseExpression?.NodeType == ExpressionType.Constant)
{
return this.Visit
(
Expression.Constant
(
GetConstantValue
(
((ConstantExpression)baseExpression).Value,
node.GetPropertyFullName(),
baseExpression.Type
)
)
);
}

return base.VisitMember(node);
}

InfoDictionary.Add(parameterExpression, TypeMappings);
return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), new List<PropertyMapInfo>());
Expand Down Expand Up @@ -145,33 +174,58 @@ protected override Expression VisitMemberInit(MemberInitExpression node)
//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

IEnumerable<MemberBinding> bindings = node.Bindings.Select
(
binding =>
{
Expression bindingExpression = ((MemberAssignment)binding).Expression;
return DoBind
//IEnumerable<MemberBinding> bindings = node.Bindings.Select
//(
// binding =>
// {
// Expression bindingExpression = ((MemberAssignment)binding).Expression;
// return DoBind
// (
// GetSourceMember(typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name)),
// bindingExpression,
// this.Visit(bindingExpression)
// );
// }
//);

IEnumerable<MemberBinding> bindings = node.Bindings.Aggregate(new List<MemberBinding>(), (list, binding) =>
{
var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == binding.Member.Name);
if (propertyMap == null)
return list;
Expression bindingExpression = ((MemberAssignment)binding).Expression;
list.Add
(
DoBind
(
typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name),
GetSourceMember(propertyMap),
bindingExpression,
this.Visit(bindingExpression)
);
}
);
)
);
return list;
});

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

return base.VisitMemberInit(node);
}

private MemberBinding DoBind(PropertyMap propertyMap, Expression initial, Expression mapped)
private MemberBinding DoBind(MemberInfo sourceMember, Expression initial, Expression mapped)
{
mapped = mapped.ConvertTypeIfNecessary(propertyMap.SourceMember.GetMemberType());
mapped = mapped.ConvertTypeIfNecessary(sourceMember.GetMemberType());
this.TypeMappings.AddTypeMapping(ConfigurationProvider, initial.Type, mapped.Type);
return Expression.Bind(propertyMap.SourceMember, mapped);
return Expression.Bind(sourceMember, mapped);
}

private MemberInfo GetSourceMember(PropertyMap propertyMap)
=> propertyMap.CustomMapExpression != null
? propertyMap.CustomMapExpression.GetMemberExpression()?.Member
: propertyMap.SourceMember;

protected override Expression VisitBinary(BinaryExpression node)
{
return DoVisitBinary(this.Visit(node.Left), this.Visit(node.Right), this.Visit(node.Conversion));
Expand Down Expand Up @@ -261,7 +315,9 @@ Expression DoVisitUnary(Expression updated)
protected override Expression VisitConstant(ConstantExpression node)
{
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType));
return base.VisitConstant(Expression.Constant(Mapper.MapObject(node.Value, node.Type, newType), newType));
//Issue 3455 (Non-Generic Mapper.Map failing for structs in v10)
//return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType));

return base.VisitConstant(node);
}
Expand Down
Loading

0 comments on commit e16014d

Please sign in to comment.