diff --git a/Directory.Build.props b/Directory.Build.props
index 409fef6..9d6b55a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
Jimmy Bogard
latest
- 3.1.2
+ 4.0.0-preview01
true
$(NoWarn);1701;1702;1591
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj
index dffca86..7c1cbee 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj
+++ b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs
index 1a373f2..e42487e 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs
@@ -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 expression, ResolutionContext context)
+ private static TDestination Map(TSource expression, ResolutionContext context, IConfigurationProvider configurationProvider)
where TSource : LambdaExpression
- where TDestination : LambdaExpression => context.Mapper.MapExpression(expression);
+ where TDestination : LambdaExpression => configurationProvider.CreateMapper().MapExpression(expression);
private static readonly MethodInfo MapMethodInfo = typeof(ExpressionMapper).GetDeclaredMethod(nameof(Map));
@@ -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
{
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs
index 9bb8c91..667d366 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs
@@ -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;
+
///
/// Returns the ParameterExpression for the LINQ parameter.
///
@@ -140,10 +143,15 @@ public static ParameterExpression GetParameterExpression(this Expression express
///
///
///
- 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;
+ }
///
/// Adds member expressions to an existing expression.
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs
index 1060192..faf2293 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs
@@ -1,3 +1,5 @@
+using AutoMapper.Internal;
+using AutoMapper.Mappers;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -5,15 +7,12 @@
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>;
using ParameterBag = IDictionary;
- using static Expression;
public class SourceInjectedQueryProvider : IQueryProvider
{
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
index 26a335c..61b14d8 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
@@ -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
{
@@ -36,10 +35,22 @@ private static TDestDelegate _MapExpression(this
where TDestDelegate : LambdaExpression
=> mapper.MapExpression(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(IMapper mapper, TSource source)
+ => mapper.Map(source);
+
+ private static MethodInfo GetMapObjectMethod(this string methodName)
+ => typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
+
///
/// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value.
///
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs
deleted file mode 100644
index cb8ad16..0000000
--- a/src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using AutoMapper.Configuration.Internal;
-
-namespace AutoMapper.Extensions.ExpressionMapping
-{
- internal static class PrimitiveExtensions
- {
- public static bool IsSetType(this Type type)
- => type.ImplementsGenericInterface(typeof(ISet<>));
-
- public static TValue GetOrDefault(this IDictionary dictionary, TKey key)
- => PrimitiveHelper.GetOrDefault(dictionary, key);
-
- public static MethodInfo GetInheritedMethod(this Type type, string name)
- => PrimitiveHelper.GetInheritedMethod(type, name);
-
- public static MemberInfo GetFieldOrProperty(this Type type, string name)
- => PrimitiveHelper.GetFieldOrProperty(type, name);
-
- public static bool IsNullableType(this Type type)
- => PrimitiveHelper.IsNullableType(type);
-
- public static Type GetTypeOfNullable(this Type type)
- => PrimitiveHelper.GetTypeOfNullable(type);
-
- public static bool IsCollectionType(this Type type)
- => PrimitiveHelper.IsCollectionType(type);
-
- public static bool IsEnumerableType(this Type type)
- => PrimitiveHelper.IsEnumerableType(type);
-
- public static bool IsQueryableType(this Type type)
- => PrimitiveHelper.IsQueryableType(type);
-
- public static bool IsListType(this Type type)
- => PrimitiveHelper.IsListType(type);
-
- public static bool IsDictionaryType(this Type type)
- => PrimitiveHelper.IsDictionaryType(type);
-
- public static bool ImplementsGenericInterface(this Type type, Type interfaceType)
- => PrimitiveHelper.ImplementsGenericInterface(type, interfaceType);
-
- public static bool IsGenericType(this Type type, Type genericType)
- => PrimitiveHelper.IsGenericType(type, genericType);
-
- public static Type GetIEnumerableType(this Type type)
- => PrimitiveHelper.GetIEnumerableType(type);
-
- public static Type GetDictionaryType(this Type type)
- => PrimitiveHelper.GetDictionaryType(type);
-
- public static Type GetGenericInterface(this Type type, Type genericInterface)
- => PrimitiveHelper.GetGenericInterface(type, genericInterface);
-
- public static Type GetGenericElementType(this Type type)
- => PrimitiveHelper.GetGenericElementType(type);
- }
-}
\ No newline at end of file
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs
index 26e2612..449bc60 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs
@@ -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);
@@ -38,27 +32,12 @@ public static MemberPaths GetMemberPaths(Type type, string[] membersToExpand) =>
public static MemberPaths GetMemberPaths(Expression>[] 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);
- ///
- /// 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
- ///
- ///
- ///
- ///
- ///
- public static Type ReplaceItemType(this Type targetType, Type oldType, Type newType)
- => ReflectionHelper.ReplaceItemType(targetType, oldType, newType);
-
public static IEnumerable GetDefinedTypes(this Assembly assembly) =>
assembly.DefinedTypes;
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs
index f0c2f06..f6d90c9 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs
@@ -1,4 +1,4 @@
-using AutoMapper.Configuration.Internal;
+using AutoMapper.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -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);
@@ -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];
}
}
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
index 4c5365e..821e7e5 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
@@ -38,11 +38,40 @@ protected override Expression VisitParameter(ParameterExpression node)
return !pair.Equals(default(KeyValuePair)) ? 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());
@@ -145,19 +174,39 @@ 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 bindings = node.Bindings.Select
- (
- binding =>
- {
- Expression bindingExpression = ((MemberAssignment)binding).Expression;
- return DoBind
+ //IEnumerable bindings = node.Bindings.Select
+ //(
+ // binding =>
+ // {
+ // Expression bindingExpression = ((MemberAssignment)binding).Expression;
+ // return DoBind
+ // (
+ // GetSourceMember(typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name)),
+ // bindingExpression,
+ // this.Visit(bindingExpression)
+ // );
+ // }
+ //);
+
+ IEnumerable bindings = node.Bindings.Aggregate(new List(), (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);
}
@@ -165,13 +214,18 @@ protected override Expression VisitMemberInit(MemberInitExpression node)
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));
@@ -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);
}
diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs
index 0055e5e..604344f 100644
--- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs
+++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs
@@ -138,7 +138,7 @@ public void Shoud_support_enumerable_return_type()
.OrderBy(s => s.DestValue).SkipWhile(d => d.DestValue < 7).Take(1)
.OrderByDescending(s => s.DestValue).Select(s => s.Strings);
- result.First().Count().ShouldBe(0);
+ result.First().ShouldBe(null);
}
diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs
index b95cf6c..fddcd56 100644
--- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs
+++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs
@@ -34,6 +34,8 @@ public void Can_map_value_types_constants_with_instance_methods()
//Act
var output1 = source.AsQueryable().GetItems(mapper, q => q.Truck.Equals(default(TruckModel)));
var output2 = source.AsQueryable().Query(mapper, q => q.First());
+
+ //Assert
output1.First().Truck.ShouldBe(default);
output2.Truck.ShouldBe(default);
}
@@ -63,6 +65,7 @@ public void Can_convert_return_type()
var output1 = source.AsQueryable().GetItems(mapper, q => q.Truck.Year == 2000).Select(g => g.Truck);
var output2 = source.AsQueryable().Query(mapper, q => q.Select(g => g.Truck).First());
+ //Assert
output1.First().Year.ShouldBe(2000);
output2.Year.ShouldBe(2000);
}
@@ -70,6 +73,7 @@ public void Can_convert_return_type()
[Fact]
public void Replace_operator_when_operands_change()
{
+ // Arrange
var config = new MapperConfiguration(cfg => {
cfg.AddExpressionMapping();
cfg.CreateMap