tools/AutoMapper/XpressionMapper/Extensions/MapperExtensions.cs (132 lines of code) (raw):
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;
namespace AutoMapper.XpressionMapper.Extensions
{
public static class MapperExtensions
{
/// <summary>
/// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression)
where TDestDelegate : LambdaExpression
{
if (expression == null)
return default(TDestDelegate);
var typeSourceFunc = expression.GetType().GetGenericArguments()[0];
var typeDestFunc = typeof(TDestDelegate).GetGenericArguments()[0];
var typeMappings = new Dictionary<Type, Type>()
.AddTypeMappingsFromDelegates(typeSourceFunc, typeDestFunc);
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);
return (TDestDelegate)Lambda(typeDestFunc, remappedBody, 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>
/// <typeparam name="TSourceDelegate"></typeparam>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> mapper.MapExpression<TDestDelegate>(expression);
/// <summary>
/// Maps an expression to be used as an "Include" given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper mapper, LambdaExpression expression)
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 (TDestDelegate)Lambda(typeDestFunc, remappedBody, expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings));
}
/// <summary>
/// Maps an expression to be used as an "Include" given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TSourceDelegate"></typeparam>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> mapper.MapExpressionAsInclude<TDestDelegate>(expression);
/// <summary>
/// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TSourceDelegate"></typeparam>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpression<TSourceDelegate, TDestDelegate>).ToList();
/// <summary>
/// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpression<TDestDelegate>).ToList();
/// <summary>
/// Maps a collection of expressions to be used as a "Includes" given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TSourceDelegate"></typeparam>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpressionAsInclude<TSourceDelegate, TDestDelegate>).ToList();
/// <summary>
/// Maps a collection of expressions to be used as a "Includes" given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapIncludesList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpressionAsInclude<TDestDelegate>).ToList();
/// <summary>
/// Takes a list of parameters from the source lamda expression and returns a list of parameters for the destination lambda expression.
/// </summary>
/// <param name="expression"></param>
/// <param name="infoDictionary"></param>
/// <param name="typeMappings"></param>
/// <returns></returns>
public static List<ParameterExpression> GetDestinationParameterExpressions(this LambdaExpression expression, MapperInfoDictionary infoDictionary, Dictionary<Type, Type> typeMappings)
{
foreach (var p in expression.Parameters.Where(p => !infoDictionary.ContainsKey(p)))
{
infoDictionary.Add(p, typeMappings);
}
return expression.Parameters.Select(p => infoDictionary[p].NewParameter).ToList();
}
/// <summary>
/// Adds a new source and destination key-value pair to a dictionary of type mappings based on the generic arguments.
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TDest"></typeparam>
/// <param name="typeMappings"></param>
/// <returns></returns>
public static Dictionary<Type, Type> AddTypeMapping<TSource, TDest>(this Dictionary<Type, Type> typeMappings)
=> typeMappings == null
? throw new ArgumentException(Resource.typeMappingsDictionaryIsNull)
: typeMappings.AddTypeMapping(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)
{
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;
});
}
/// <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="sourceType"></param>
/// <param name="destType"></param>
/// <returns></returns>
public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
{
if (typeMappings == null)
throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
if (sourceType.GetTypeInfo().IsGenericType && sourceType.GetGenericTypeDefinition() == typeof(Expression<>))
{
sourceType = sourceType.GetGenericArguments()[0];
destType = destType.GetGenericArguments()[0];
}
if (!typeMappings.ContainsKey(sourceType) && sourceType != destType)
{
typeMappings.Add(sourceType, destType);
if (typeof(Delegate).IsAssignableFrom(sourceType))
typeMappings.AddTypeMappingsFromDelegates(sourceType, destType);
else
typeMappings.AddUnderlyingTypes(sourceType, destType);
}
return typeMappings;
}
private static Dictionary<Type, Type> AddTypeMappingsFromDelegates(this Dictionary<Type, Type> typeMappings, Type sourceType, Type destType)
{
if (typeMappings == null)
throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
var sourceArguments = sourceType.GetGenericArguments().ToList();
var destArguments = destType.GetGenericArguments().ToList();
if (sourceArguments.Count != destArguments.Count)
throw new ArgumentException(Resource.invalidArgumentCount);
return sourceArguments.Aggregate(typeMappings, (dic, next) =>
{
if (!dic.ContainsKey(next) && next != destArguments[sourceArguments.IndexOf(next)])
dic.AddTypeMapping(next, destArguments[sourceArguments.IndexOf(next)]);
return dic;
});
}
}
}