Skip to content

Improves mapping of constants in expressions. #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ namespace AutoMapper.Extensions.ExpressionMapping
{
public class MapIncludesVisitor : XpressionMapperVisitor
{
public MapIncludesVisitor(IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings)
: base(configurationProvider, typeMappings)
public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings)
: base(mapper, configurationProvider, typeMappings)
{
}

Expand Down
187 changes: 126 additions & 61 deletions src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using static System.Linq.Expressions.Expression;
using System.Reflection;
using AutoMapper.Mappers.Internal;
using AutoMapper.Internal;

namespace AutoMapper.Extensions.ExpressionMapping
{
Expand All @@ -21,22 +22,60 @@ public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, La
where TDestDelegate : LambdaExpression
{
if (expression == null)
return default(TDestDelegate);
return default;

var typeSourceFunc = expression.GetType().GetGenericArguments()[0];
var typeDestFunc = typeof(TDestDelegate).GetGenericArguments()[0];
return mapper.MapExpression<TDestDelegate>
(
expression,
(config, mappings) => new XpressionMapperVisitor(mapper, config, mappings)
);
}

private static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression, Func<IConfigurationProvider, Dictionary<Type, Type>, XpressionMapperVisitor> getVisitor)
where TDestDelegate : LambdaExpression
{
return MapExpression<TDestDelegate>
(
mapper ?? Mapper.Instance,
mapper == null ? Mapper.Configuration : mapper.ConfigurationProvider,
expression,
expression.GetType().GetGenericArguments()[0],
typeof(TDestDelegate).GetGenericArguments()[0],
getVisitor
);
}

var typeMappings = new Dictionary<Type, Type>()
.AddTypeMappingsFromDelegates(typeSourceFunc, typeDestFunc);
private static TDestDelegate MapExpression<TDestDelegate>(IMapper mapper,
IConfigurationProvider configurationProvider,
LambdaExpression expression,
Type typeSourceFunc,
Type typeDestFunc,
Func<IConfigurationProvider, Dictionary<Type, Type>, XpressionMapperVisitor> getVisitor)
where TDestDelegate : LambdaExpression
{
return CreateVisitor(new Dictionary<Type, Type>().AddTypeMappingsFromDelegates(configurationProvider, typeSourceFunc, typeDestFunc));

TDestDelegate CreateVisitor(Dictionary<Type, Type> typeMappings)
=> MapBody(typeMappings, getVisitor(configurationProvider, typeMappings));

var visitor = new XpressionMapperVisitor(mapper == null ? Mapper.Configuration : mapper.ConfigurationProvider, typeMappings);
var remappedBody = visitor.Visit(expression.Body);
if (remappedBody == null)
throw new InvalidOperationException(Resource.cantRemapExpression);
TDestDelegate MapBody(Dictionary<Type, Type> typeMappings, XpressionMapperVisitor visitor)
=> GetLambda(typeMappings, visitor, visitor.Visit(expression.Body));

return (TDestDelegate)Lambda(typeDestFunc, remappedBody, expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings));
TDestDelegate GetLambda(Dictionary<Type, Type> typeMappings, XpressionMapperVisitor visitor, Expression remappedBody)
{
if (remappedBody == null)
throw new InvalidOperationException(Resource.cantRemapExpression);

return (TDestDelegate)Lambda
(
typeDestFunc,
ExpressionFactory.ToType(remappedBody, typeDestFunc.GetGenericArguments().Last()),
expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings)
);
}
}


/// <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 All @@ -47,7 +86,7 @@ public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, La
/// <returns></returns>
public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> mapper.MapExpression<TDestDelegate>(expression);

/// <summary>
Expand All @@ -61,20 +100,13 @@ public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper m
where TDestDelegate : LambdaExpression
{
if (expression == null)
return null;

var typeSourceFunc = expression.GetType().GetGenericArguments()[0];
var typeDestFunc = typeof(TDestDelegate).GetGenericArguments()[0];

var typeMappings = new Dictionary<Type, Type>()
.AddTypeMappingsFromDelegates(typeSourceFunc, typeDestFunc);

XpressionMapperVisitor visitor = new MapIncludesVisitor(mapper == null ? Mapper.Configuration : mapper.ConfigurationProvider, typeMappings);
var remappedBody = visitor.Visit(expression.Body);
if (remappedBody == null)
throw new InvalidOperationException(Resource.cantRemapExpression);
return default;

return (TDestDelegate)Lambda(typeDestFunc, remappedBody, expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings));
return mapper.MapExpression<TDestDelegate>
(
expression,
(config, mappings) => new MapIncludesVisitor(mapper, config, mappings)
);
}

/// <summary>
Expand All @@ -87,7 +119,7 @@ public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper m
/// <returns></returns>
public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> mapper.MapExpressionAsInclude<TDestDelegate>(expression);

/// <summary>
Expand All @@ -100,7 +132,7 @@ public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegat
/// <returns></returns>
public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpression<TSourceDelegate, TDestDelegate>).ToList();

/// <summary>
Expand All @@ -111,7 +143,7 @@ public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDes
/// <param name="collection"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
where TDestDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpression<TDestDelegate>).ToList();

/// <summary>
Expand All @@ -124,7 +156,7 @@ public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this I
/// <returns></returns>
public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpressionAsInclude<TSourceDelegate, TDestDelegate>).ToList();

/// <summary>
Expand All @@ -135,7 +167,7 @@ public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestD
/// <param name="collection"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapIncludesList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
where TDestDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpressionAsInclude<TDestDelegate>).ToList();

/// <summary>
Expand All @@ -161,47 +193,37 @@ public static List<ParameterExpression> GetDestinationParameterExpressions(this
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TDest"></typeparam>
/// <param name="typeMappings"></param>
/// <param name="configurationProvider"></param>
/// <returns></returns>
public static Dictionary<Type, Type> AddTypeMapping<TSource, TDest>(this Dictionary<Type, Type> typeMappings)
public static Dictionary<Type, Type> AddTypeMapping<TSource, TDest>(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider)
=> typeMappings == null
? throw new ArgumentException(Resource.typeMappingsDictionaryIsNull)
: typeMappings.AddTypeMapping(typeof(TSource), typeof(TDest));
: typeMappings.AddTypeMapping(configurationProvider, typeof(TSource), typeof(TDest));

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

private static void AddUnderlyingTypes(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
private static void AddUnderlyingTypes(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
{
var sourceArguments = !sourceType.HasUnderlyingType()
? new List<Type>()
: ElementTypeHelper.GetElementTypes(sourceType).ToList();

var destArguments = !destType.HasUnderlyingType()
? new List<Type>()
: ElementTypeHelper.GetElementTypes(destType).ToList();

if (sourceArguments.Count != destArguments.Count)
throw new ArgumentException(Resource.invalidArgumentCount);

sourceArguments.Aggregate(typeMappings, (dic, next) =>
{
if (!dic.ContainsKey(next) && next != destArguments[sourceArguments.IndexOf(next)])
dic.AddTypeMapping(next, destArguments[sourceArguments.IndexOf(next)]);

return dic;
});
typeMappings.DoAddTypeMappings
(
configurationProvider,
!sourceType.HasUnderlyingType() ? new List<Type>() : ElementTypeHelper.GetElementTypes(sourceType).ToList(),
!destType.HasUnderlyingType() ? new List<Type>() : ElementTypeHelper.GetElementTypes(destType).ToList()
);
}

/// <summary>
/// Adds a new source and destination key-value pair to a dictionary of type mappings based on the arguments.
/// </summary>
/// <param name="typeMappings"></param>
/// <param name="configurationProvider"></param>
/// <param name="sourceType"></param>
/// <param name="destType"></param>
/// <returns></returns>
public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
{
if (typeMappings == null)
throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
Expand All @@ -216,32 +238,75 @@ public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type>
{
typeMappings.Add(sourceType, destType);
if (typeof(Delegate).IsAssignableFrom(sourceType))
typeMappings.AddTypeMappingsFromDelegates(sourceType, destType);
typeMappings.AddTypeMappingsFromDelegates(configurationProvider, sourceType, destType);
else
typeMappings.AddUnderlyingTypes(sourceType, destType);
{
typeMappings.AddUnderlyingTypes(configurationProvider, sourceType, destType);
typeMappings.FindChildPropertyTypeMaps(configurationProvider, sourceType, destType);
}
}

return typeMappings;
}

private static Dictionary<Type, Type> AddTypeMappingsFromDelegates(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
private static Dictionary<Type, Type> AddTypeMappingsFromDelegates(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
{
if (typeMappings == null)
throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);

var sourceArguments = sourceType.GetGenericArguments().ToList();
var destArguments = destType.GetGenericArguments().ToList();
typeMappings.DoAddTypeMappings
(
configurationProvider,
sourceType.GetGenericArguments().ToList(),
destType.GetGenericArguments().ToList()
);

return typeMappings;
}

private static void DoAddTypeMappings(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, List<Type> sourceArguments, List<Type> destArguments)
{
if (sourceArguments.Count != destArguments.Count)
throw new ArgumentException(Resource.invalidArgumentCount);

return sourceArguments.Aggregate(typeMappings, (dic, next) =>
for (int i = 0; i < sourceArguments.Count; i++)
{
if (!dic.ContainsKey(next) && next != destArguments[sourceArguments.IndexOf(next)])
dic.AddTypeMapping(next, destArguments[sourceArguments.IndexOf(next)]);
if (!typeMappings.ContainsKey(sourceArguments[i]) && sourceArguments[i] != destArguments[i])
typeMappings.AddTypeMapping(configurationProvider, sourceArguments[i], destArguments[i]);
}
}

return dic;
});
private static void FindChildPropertyTypeMaps(this Dictionary<Type, Type> typeMappings, IConfigurationProvider ConfigurationProvider, Type source, Type dest)
{
//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
var typeMap = ConfigurationProvider.ResolveTypeMap(sourceType: dest, destinationType: source);

if (typeMap == null)
return;

FindMaps(typeMap.GetPropertyMaps().ToList());
void FindMaps(List<PropertyMap> maps)
{
foreach (PropertyMap pm in maps)
{
if (pm.SourceMember == null)
continue;

AddChildMappings
(
source.GetFieldOrProperty(pm.DestinationProperty.Name).GetMemberType(),
pm.SourceMember.GetMemberType()
);
void AddChildMappings(Type sourcePropertyType, Type destPropertyType)
{
if (sourcePropertyType.IsLiteralType() || destPropertyType.IsLiteralType())
return;

typeMappings.AddTypeMapping(ConfigurationProvider, sourcePropertyType, destPropertyType);
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/AutoMapper.Extensions.ExpressionMapping/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
<value>Can't rempa expression</value>
</data>
<data name="expressionMapValueTypeMustMatchFormat" xml:space="preserve">
<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>
<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>
<comment>0=Source Type; 1=SourceDescription; 2=Destination Type; 3=Destination Property.</comment>
</data>
<data name="includeExpressionTooComplex" xml:space="preserve">
Expand Down
31 changes: 30 additions & 1 deletion src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using AutoMapper.Configuration.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -126,7 +127,35 @@ public static bool IsNotPublic(this ConstructorInfo constructorInfo) => construc

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

public static bool IsLiteralType(this Type type) => type == typeof(string) || type.GetTypeInfo().IsValueType;
public static bool IsLiteralType(this Type type)
{
if (PrimitiveHelper.IsNullableType(type))
type = Nullable.GetUnderlyingType(type);

return LiteralTypes.Contains(type);
}

private static HashSet<Type> LiteralTypes => new HashSet<Type>(_literalTypes);

private static Type[] _literalTypes => new Type[] {
typeof(bool),
typeof(DateTime),
typeof(TimeSpan),
typeof(Guid),
typeof(decimal),
typeof(byte),
typeof(short),
typeof(int),
typeof(long),
typeof(float),
typeof(double),
typeof(char),
typeof(sbyte),
typeof(ushort),
typeof(uint),
typeof(ulong),
typeof(string)
};

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

Expand Down
Loading