Skip to content

Commit 2768e95

Browse files
authored
Fixes issue #158 - incorrect arguments passed to Expression.Call. (#159)
* Fixes issue #158 - incorrect arguments passed to Expression.Call. * Mapping array constant.
1 parent 6524c15 commit 2768e95

File tree

4 files changed

+264
-7
lines changed

4 files changed

+264
-7
lines changed

src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,22 +335,39 @@ void AddTypeMaps(TypeMap typeMap)
335335
/// <returns></returns>
336336
public static Type ReplaceType(this Dictionary<Type, Type> typeMappings, Type sourceType)
337337
{
338-
if (!sourceType.IsGenericType)
338+
if (sourceType.IsArray)
339339
{
340-
return typeMappings.TryGetValue(sourceType, out Type destType) ? destType : sourceType;
340+
if (typeMappings.TryGetValue(sourceType, out Type destType))
341+
return destType;
342+
343+
if (typeMappings.TryGetValue(sourceType.GetElementType(), out Type destElementType))
344+
{
345+
int rank = sourceType.GetArrayRank();
346+
return rank == 1
347+
? destElementType.MakeArrayType()
348+
: destElementType.MakeArrayType(rank);
349+
}
350+
351+
return sourceType;
341352
}
342-
else
353+
else if (sourceType.IsGenericType)
343354
{
344355
if (typeMappings.TryGetValue(sourceType, out Type destType))
345356
return destType;
346357
else
358+
{
347359
return sourceType.GetGenericTypeDefinition().MakeGenericType
348360
(
349361
sourceType
350362
.GetGenericArguments()
351-
.Select(type => typeMappings.ReplaceType(type))
363+
.Select(typeMappings.ReplaceType)
352364
.ToArray()
353365
);
366+
}
367+
}
368+
else
369+
{
370+
return typeMappings.TryGetValue(sourceType, out Type destType) ? destType : sourceType;
354371
}
355372
}
356373

src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,52 @@ namespace AutoMapper.Extensions.ExpressionMapping
66
{
77
internal static class TypeMapHelper
88
{
9+
public static bool CanMapConstant(this IConfigurationProvider config, Type sourceType, Type destType)
10+
{
11+
if (sourceType == destType)
12+
return false;
13+
14+
if (BothTypesAreDictionary())
15+
{
16+
Type[] sourceGenericTypes = sourceType.GetGenericArguments();
17+
Type[] destGenericTypes = destType.GetGenericArguments();
18+
if (sourceGenericTypes.SequenceEqual(destGenericTypes))
19+
return false;
20+
else if (sourceGenericTypes[0] == destGenericTypes[0])
21+
return config.CanMapConstant(sourceGenericTypes[1], destGenericTypes[1]);
22+
else if (sourceGenericTypes[1] == destGenericTypes[1])
23+
return config.CanMapConstant(sourceGenericTypes[0], destGenericTypes[0]);
24+
else
25+
return config.CanMapConstant(sourceGenericTypes[0], destGenericTypes[0]) && config.CanMapConstant(sourceGenericTypes[1], destGenericTypes[1]);
26+
}
27+
else if (sourceType.IsArray && destType.IsArray)
28+
return config.CanMapConstant(sourceType.GetElementType(), destType.GetElementType());
29+
else if (BothTypesAreEnumerable())
30+
return config.CanMapConstant(sourceType.GetGenericArguments()[0], destType.GetGenericArguments()[0]);
31+
else
32+
return config.Internal().ResolveTypeMap(sourceType, destType) != null;
33+
34+
bool BothTypesAreEnumerable()
35+
{
36+
Type enumerableType = typeof(System.Collections.IEnumerable);
37+
return sourceType.IsGenericType
38+
&& destType.IsGenericType
39+
&& enumerableType.IsAssignableFrom(sourceType)
40+
&& enumerableType.IsAssignableFrom(destType);
41+
}
42+
43+
bool BothTypesAreDictionary()
44+
{
45+
Type dictionaryType = typeof(System.Collections.IDictionary);
46+
return sourceType.IsGenericType
47+
&& destType.IsGenericType
48+
&& dictionaryType.IsAssignableFrom(sourceType)
49+
&& dictionaryType.IsAssignableFrom(destType)
50+
&& sourceType.GetGenericArguments().Length == 2
51+
&& destType.GetGenericArguments().Length == 2;
52+
}
53+
}
54+
955
public static MemberMap GetMemberMapByDestinationProperty(this TypeMap typeMap, string destinationPropertyName)
1056
{
1157
var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == destinationPropertyName);

src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,13 @@ Expression DoVisitUnary(Expression updated)
511511

512512
protected override Expression VisitConstant(ConstantExpression node)
513513
{
514-
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
514+
Type newType = this.TypeMappings.ReplaceType(node.Type);
515+
if (newType != node.Type)
515516
{
516517
if (node.Value == null)
517518
return base.VisitConstant(Expression.Constant(null, newType));
518519

519-
if (ConfigurationProvider.Internal().ResolveTypeMap(node.Type, newType) != null)
520+
if (ConfigurationProvider.CanMapConstant(node.Type, newType))
520521
return base.VisitConstant(Expression.Constant(Mapper.MapObject(node.Value, node.Type, newType), newType));
521522
//Issue 3455 (Non-Generic Mapper.Map failing for structs in v10)
522523
//return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType));
@@ -553,7 +554,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
553554
MethodCallExpression GetInstanceExpression(Expression instance)
554555
=> node.Method.IsGenericMethod
555556
? Expression.Call(instance, node.Method.Name, typeArgsForNewMethod.ToArray(), listOfArgumentsForNewMethod.ToArray())
556-
: Expression.Call(instance, node.Method, listOfArgumentsForNewMethod.ToArray());
557+
: Expression.Call(instance, instance.Type.GetMethod(node.Method.Name, listOfArgumentsForNewMethod.Select(a => a.Type).ToArray()), listOfArgumentsForNewMethod.ToArray());
557558

558559
MethodCallExpression GetStaticExpression()
559560
=> node.Method.IsGenericMethod
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using Xunit;
6+
7+
namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
8+
{
9+
public class CanMapExpressionWithListConstants
10+
{
11+
[Fact]
12+
public void Map_expression_with_constant_array()
13+
{
14+
//Arrange
15+
var config = new MapperConfiguration
16+
(
17+
cfg =>
18+
{
19+
cfg.CreateMap<EntityModel, Entity>();
20+
cfg.CreateMap<Entity, EntityModel>();
21+
}
22+
);
23+
config.AssertConfigurationIsValid();
24+
var mapper = config.CreateMapper();
25+
List<EntityModel> source1 = new() {
26+
new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
27+
};
28+
List<EntityModel> source2 = new() {
29+
new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
30+
};
31+
Entity[] entities = new Entity[] { new Entity { SimpleEnum = SimpleEnum.Value1 }, new Entity { SimpleEnum = SimpleEnum.Value2 } };
32+
Expression<Func<Entity, bool>> filter = e => entities.Any(en => e.SimpleEnum == en.SimpleEnum);
33+
34+
//act
35+
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);
36+
37+
//assert
38+
Assert.False(source1.AsQueryable().Any(mappedFilter));
39+
Assert.True(source2.AsQueryable().Any(mappedFilter));
40+
}
41+
42+
[Fact]
43+
public void Map_expression_with_constant_list_using_generic_list_dot_contains()
44+
{
45+
//Arrange
46+
var config = new MapperConfiguration
47+
(
48+
cfg =>
49+
{
50+
cfg.CreateMap<EntityModel, Entity>();
51+
cfg.CreateMap<SimpleEnum, SimpleEnumModel>();
52+
}
53+
);
54+
config.AssertConfigurationIsValid();
55+
var mapper = config.CreateMapper();
56+
List<EntityModel> source1 = new() {
57+
new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
58+
};
59+
List<EntityModel> source2 = new() {
60+
new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
61+
};
62+
List<SimpleEnum> enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 };
63+
Expression<Func<Entity, bool>> filter = e => enums.Contains(e.SimpleEnum);
64+
65+
//act
66+
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);
67+
68+
//assert
69+
Assert.False(source1.AsQueryable().Any(mappedFilter));
70+
Assert.True(source2.AsQueryable().Any(mappedFilter));
71+
}
72+
73+
[Fact]
74+
public void Map_expression_with_constant_list_using_generic_enumerable_dot_contains()
75+
{
76+
//Arrange
77+
var config = new MapperConfiguration
78+
(
79+
cfg =>
80+
{
81+
cfg.CreateMap<EntityModel, Entity>();
82+
cfg.CreateMap<SimpleEnum, SimpleEnumModel>();
83+
}
84+
);
85+
config.AssertConfigurationIsValid();
86+
var mapper = config.CreateMapper();
87+
List<EntityModel> source1 = new() {
88+
new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
89+
};
90+
List<EntityModel> source2 = new() {
91+
new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
92+
};
93+
List<SimpleEnum> enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 };
94+
Expression<Func<Entity, bool>> filter = e => Enumerable.Contains(enums, e.SimpleEnum);
95+
96+
//act
97+
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);
98+
99+
//assert
100+
Assert.False(source1.AsQueryable().Any(mappedFilter));
101+
Assert.True(source2.AsQueryable().Any(mappedFilter));
102+
}
103+
104+
[Fact]
105+
public void Map_expression_with_constant_dictionary()
106+
{
107+
//Arrange
108+
var config = new MapperConfiguration
109+
(
110+
cfg =>
111+
{
112+
cfg.CreateMap<EntityModel, Entity>();
113+
cfg.CreateMap<SimpleEnum, SimpleEnumModel>();
114+
}
115+
);
116+
config.AssertConfigurationIsValid();
117+
var mapper = config.CreateMapper();
118+
List<EntityModel> source1 = new() {
119+
new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
120+
};
121+
List<EntityModel> source2 = new() {
122+
new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
123+
};
124+
Dictionary<string, SimpleEnum> enumDictionary = new() { ["A"] = SimpleEnum.Value1, ["B"] = SimpleEnum.Value2 };
125+
Expression<Func<Entity, bool>> filter = e => enumDictionary.Any(i => i.Value == e.SimpleEnum);
126+
127+
//act
128+
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);
129+
130+
//assert
131+
Assert.False(source1.AsQueryable().Any(mappedFilter));
132+
Assert.True(source2.AsQueryable().Any(mappedFilter));
133+
}
134+
135+
[Fact]
136+
public void Map_expression_with_constant_dictionary_mapping_both_Key_and_value()
137+
{
138+
//Arrange
139+
var config = new MapperConfiguration
140+
(
141+
cfg =>
142+
{
143+
cfg.CreateMap<EntityModel, Entity>();
144+
cfg.CreateMap<Entity, EntityModel>();
145+
cfg.CreateMap<SimpleEnum, SimpleEnumModel>();
146+
}
147+
);
148+
config.AssertConfigurationIsValid();
149+
var mapper = config.CreateMapper();
150+
List<EntityModel> source1 = new() {
151+
new EntityModel { SimpleEnum = SimpleEnumModel.Value3 }
152+
};
153+
List<EntityModel> source2 = new() {
154+
new EntityModel { SimpleEnum = SimpleEnumModel.Value1 }
155+
};
156+
Dictionary<SimpleEnum, Entity> enumDictionary = new() { [SimpleEnum.Value1] = new Entity { SimpleEnum = SimpleEnum.Value1 }, [SimpleEnum.Value2] = new Entity { SimpleEnum = SimpleEnum.Value2 } };
157+
Expression<Func<Entity, bool>> filter = e => enumDictionary.Any(i => i.Key == e.SimpleEnum && i.Value.SimpleEnum == e.SimpleEnum);
158+
159+
//act
160+
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);
161+
162+
//assert
163+
Assert.False(source1.AsQueryable().Any(mappedFilter));
164+
Assert.True(source2.AsQueryable().Any(mappedFilter));
165+
}
166+
167+
public enum SimpleEnum
168+
{
169+
Value1,
170+
Value2,
171+
Value3
172+
}
173+
174+
public record Entity
175+
{
176+
public int Id { get; init; }
177+
public SimpleEnum SimpleEnum { get; init; }
178+
}
179+
180+
public enum SimpleEnumModel
181+
{
182+
Value1,
183+
Value2,
184+
Value3
185+
}
186+
187+
public record EntityModel
188+
{
189+
public int Id { get; init; }
190+
public SimpleEnumModel SimpleEnum { get; init; }
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)