diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
index acf9c5a..72ba3d0 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
@@ -335,22 +335,39 @@ void AddTypeMaps(TypeMap typeMap)
///
public static Type ReplaceType(this Dictionary typeMappings, Type sourceType)
{
- if (!sourceType.IsGenericType)
+ if (sourceType.IsArray)
{
- return typeMappings.TryGetValue(sourceType, out Type destType) ? destType : sourceType;
+ if (typeMappings.TryGetValue(sourceType, out Type destType))
+ return destType;
+
+ if (typeMappings.TryGetValue(sourceType.GetElementType(), out Type destElementType))
+ {
+ int rank = sourceType.GetArrayRank();
+ return rank == 1
+ ? destElementType.MakeArrayType()
+ : destElementType.MakeArrayType(rank);
+ }
+
+ return sourceType;
}
- else
+ else if (sourceType.IsGenericType)
{
if (typeMappings.TryGetValue(sourceType, out Type destType))
return destType;
else
+ {
return sourceType.GetGenericTypeDefinition().MakeGenericType
(
sourceType
.GetGenericArguments()
- .Select(type => typeMappings.ReplaceType(type))
+ .Select(typeMappings.ReplaceType)
.ToArray()
);
+ }
+ }
+ else
+ {
+ return typeMappings.TryGetValue(sourceType, out Type destType) ? destType : sourceType;
}
}
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs
index d559096..65df684 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs
@@ -6,6 +6,52 @@ namespace AutoMapper.Extensions.ExpressionMapping
{
internal static class TypeMapHelper
{
+ public static bool CanMapConstant(this IConfigurationProvider config, Type sourceType, Type destType)
+ {
+ if (sourceType == destType)
+ return false;
+
+ if (BothTypesAreDictionary())
+ {
+ Type[] sourceGenericTypes = sourceType.GetGenericArguments();
+ Type[] destGenericTypes = destType.GetGenericArguments();
+ if (sourceGenericTypes.SequenceEqual(destGenericTypes))
+ return false;
+ else if (sourceGenericTypes[0] == destGenericTypes[0])
+ return config.CanMapConstant(sourceGenericTypes[1], destGenericTypes[1]);
+ else if (sourceGenericTypes[1] == destGenericTypes[1])
+ return config.CanMapConstant(sourceGenericTypes[0], destGenericTypes[0]);
+ else
+ return config.CanMapConstant(sourceGenericTypes[0], destGenericTypes[0]) && config.CanMapConstant(sourceGenericTypes[1], destGenericTypes[1]);
+ }
+ else if (sourceType.IsArray && destType.IsArray)
+ return config.CanMapConstant(sourceType.GetElementType(), destType.GetElementType());
+ else if (BothTypesAreEnumerable())
+ return config.CanMapConstant(sourceType.GetGenericArguments()[0], destType.GetGenericArguments()[0]);
+ else
+ return config.Internal().ResolveTypeMap(sourceType, destType) != null;
+
+ bool BothTypesAreEnumerable()
+ {
+ Type enumerableType = typeof(System.Collections.IEnumerable);
+ return sourceType.IsGenericType
+ && destType.IsGenericType
+ && enumerableType.IsAssignableFrom(sourceType)
+ && enumerableType.IsAssignableFrom(destType);
+ }
+
+ bool BothTypesAreDictionary()
+ {
+ Type dictionaryType = typeof(System.Collections.IDictionary);
+ return sourceType.IsGenericType
+ && destType.IsGenericType
+ && dictionaryType.IsAssignableFrom(sourceType)
+ && dictionaryType.IsAssignableFrom(destType)
+ && sourceType.GetGenericArguments().Length == 2
+ && destType.GetGenericArguments().Length == 2;
+ }
+ }
+
public static MemberMap GetMemberMapByDestinationProperty(this TypeMap typeMap, string destinationPropertyName)
{
var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == destinationPropertyName);
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
index ecf33ef..ff354d7 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
@@ -511,12 +511,13 @@ Expression DoVisitUnary(Expression updated)
protected override Expression VisitConstant(ConstantExpression node)
{
- if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
+ Type newType = this.TypeMappings.ReplaceType(node.Type);
+ if (newType != node.Type)
{
if (node.Value == null)
return base.VisitConstant(Expression.Constant(null, newType));
- if (ConfigurationProvider.Internal().ResolveTypeMap(node.Type, newType) != null)
+ if (ConfigurationProvider.CanMapConstant(node.Type, 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));
@@ -553,7 +554,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
MethodCallExpression GetInstanceExpression(Expression instance)
=> node.Method.IsGenericMethod
? Expression.Call(instance, node.Method.Name, typeArgsForNewMethod.ToArray(), listOfArgumentsForNewMethod.ToArray())
- : Expression.Call(instance, node.Method, listOfArgumentsForNewMethod.ToArray());
+ : Expression.Call(instance, instance.Type.GetMethod(node.Method.Name, listOfArgumentsForNewMethod.Select(a => a.Type).ToArray()), listOfArgumentsForNewMethod.ToArray());
MethodCallExpression GetStaticExpression()
=> node.Method.IsGenericMethod
diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs
new file mode 100644
index 0000000..8573476
--- /dev/null
+++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using Xunit;
+
+namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
+{
+ public class CanMapExpressionWithListConstants
+ {
+ [Fact]
+ public void Map_expression_with_constant_array()
+ {
+ //Arrange
+ var config = new MapperConfiguration
+ (
+ cfg =>
+ {
+ cfg.CreateMap();
+ cfg.CreateMap();
+ }
+ );
+ config.AssertConfigurationIsValid();
+ var mapper = config.CreateMapper();
+ List source1 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
+ };
+ List source2 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
+ };
+ Entity[] entities = new Entity[] { new Entity { SimpleEnum = SimpleEnum.Value1 }, new Entity { SimpleEnum = SimpleEnum.Value2 } };
+ Expression> filter = e => entities.Any(en => e.SimpleEnum == en.SimpleEnum);
+
+ //act
+ Expression> mappedFilter = mapper.MapExpression>>(filter);
+
+ //assert
+ Assert.False(source1.AsQueryable().Any(mappedFilter));
+ Assert.True(source2.AsQueryable().Any(mappedFilter));
+ }
+
+ [Fact]
+ public void Map_expression_with_constant_list_using_generic_list_dot_contains()
+ {
+ //Arrange
+ var config = new MapperConfiguration
+ (
+ cfg =>
+ {
+ cfg.CreateMap();
+ cfg.CreateMap();
+ }
+ );
+ config.AssertConfigurationIsValid();
+ var mapper = config.CreateMapper();
+ List source1 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
+ };
+ List source2 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
+ };
+ List enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 };
+ Expression> filter = e => enums.Contains(e.SimpleEnum);
+
+ //act
+ Expression> mappedFilter = mapper.MapExpression>>(filter);
+
+ //assert
+ Assert.False(source1.AsQueryable().Any(mappedFilter));
+ Assert.True(source2.AsQueryable().Any(mappedFilter));
+ }
+
+ [Fact]
+ public void Map_expression_with_constant_list_using_generic_enumerable_dot_contains()
+ {
+ //Arrange
+ var config = new MapperConfiguration
+ (
+ cfg =>
+ {
+ cfg.CreateMap();
+ cfg.CreateMap();
+ }
+ );
+ config.AssertConfigurationIsValid();
+ var mapper = config.CreateMapper();
+ List source1 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
+ };
+ List source2 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
+ };
+ List enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 };
+ Expression> filter = e => Enumerable.Contains(enums, e.SimpleEnum);
+
+ //act
+ Expression> mappedFilter = mapper.MapExpression>>(filter);
+
+ //assert
+ Assert.False(source1.AsQueryable().Any(mappedFilter));
+ Assert.True(source2.AsQueryable().Any(mappedFilter));
+ }
+
+ [Fact]
+ public void Map_expression_with_constant_dictionary()
+ {
+ //Arrange
+ var config = new MapperConfiguration
+ (
+ cfg =>
+ {
+ cfg.CreateMap();
+ cfg.CreateMap();
+ }
+ );
+ config.AssertConfigurationIsValid();
+ var mapper = config.CreateMapper();
+ List source1 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
+ };
+ List source2 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
+ };
+ Dictionary enumDictionary = new() { ["A"] = SimpleEnum.Value1, ["B"] = SimpleEnum.Value2 };
+ Expression> filter = e => enumDictionary.Any(i => i.Value == e.SimpleEnum);
+
+ //act
+ Expression> mappedFilter = mapper.MapExpression>>(filter);
+
+ //assert
+ Assert.False(source1.AsQueryable().Any(mappedFilter));
+ Assert.True(source2.AsQueryable().Any(mappedFilter));
+ }
+
+ [Fact]
+ public void Map_expression_with_constant_dictionary_mapping_both_Key_and_value()
+ {
+ //Arrange
+ var config = new MapperConfiguration
+ (
+ cfg =>
+ {
+ cfg.CreateMap();
+ cfg.CreateMap();
+ cfg.CreateMap();
+ }
+ );
+ config.AssertConfigurationIsValid();
+ var mapper = config.CreateMapper();
+ List source1 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
+ };
+ List source2 = new() {
+ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
+ };
+ Dictionary enumDictionary = new() { [SimpleEnum.Value1] = new Entity { SimpleEnum = SimpleEnum.Value1 }, [SimpleEnum.Value2] = new Entity { SimpleEnum = SimpleEnum.Value2 } };
+ Expression> filter = e => enumDictionary.Any(i => i.Key == e.SimpleEnum && i.Value.SimpleEnum == e.SimpleEnum);
+
+ //act
+ Expression> mappedFilter = mapper.MapExpression>>(filter);
+
+ //assert
+ Assert.False(source1.AsQueryable().Any(mappedFilter));
+ Assert.True(source2.AsQueryable().Any(mappedFilter));
+ }
+
+ public enum SimpleEnum
+ {
+ Value1,
+ Value2,
+ Value3
+ }
+
+ public record Entity
+ {
+ public int Id { get; init; }
+ public SimpleEnum SimpleEnum { get; init; }
+ }
+
+ public enum SimpleEnumModel
+ {
+ Value1,
+ Value2,
+ Value3
+ }
+
+ public record EntityModel
+ {
+ public int Id { get; init; }
+ public SimpleEnumModel SimpleEnum { get; init; }
+ }
+ }
+}