Skip to content

Commit 8266800

Browse files
ErikGjerserikgjchalmersErik GjersBlaiseD
authored
Added enum to int conversion for unmapped side of binary expressions (#163)
* Added enum to int conversion for unmapped side of binary expressions * Enum to int conversion logic from VisitBinary to VisitConstant. * new test start * Made tests using Theory * Logic already in VisitMember solves the same problem - should be safer to reuse. --------- Co-authored-by: Erik Gjers <[email protected]> Co-authored-by: Erik Gjers <[email protected]> Co-authored-by: Blaise Taylor <[email protected]>
1 parent c5c1a1d commit 8266800

File tree

3 files changed

+297
-2
lines changed

3 files changed

+297
-2
lines changed

src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Reflection;
66
using System.Runtime.CompilerServices;
77
using AutoMapper.Extensions.ExpressionMapping.Structures;
8+
using AutoMapper.Internal;
89

910
namespace AutoMapper.Extensions.ExpressionMapping.Extensions
1011
{
@@ -202,5 +203,13 @@ public static List<Type> GetUnderlyingGenericTypes(this Type type) =>
202203
type == null || !type.GetTypeInfo().IsGenericType
203204
? new List<Type>()
204205
: type.GetGenericArguments().ToList();
206+
207+
public static bool IsEnumType(this Type type)
208+
{
209+
if (type.IsNullableType())
210+
type = Nullable.GetUnderlyingType(type);
211+
212+
return type.IsEnum();
213+
}
205214
}
206215
}

src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,15 @@ Expression GetMappedMemberExpression(Expression parentExpression, List<PropertyM
9595
mappedParentExpression
9696
);
9797

98-
if (node.Type.IsLiteralType())
98+
if (ShouldConvertMemberExpression(node.Type, fromCustomExpression.Type))
9999
fromCustomExpression = fromCustomExpression.ConvertTypeIfNecessary(node.Type);
100100

101101
this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, fromCustomExpression.Type);
102102
return fromCustomExpression;
103103
}
104104

105105
Expression memberExpression = GetMemberExpressionFromMemberMaps(BuildFullName(propertyMapInfoList), mappedParentExpression);
106-
if (node.Type.IsLiteralType())
106+
if (ShouldConvertMemberExpression(node.Type, memberExpression.Type))
107107
memberExpression = memberExpression.ConvertTypeIfNecessary(node.Type);
108108

109109
this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, memberExpression.Type);
@@ -140,6 +140,23 @@ Expression PrependParentMemberExpression(PrependParentNameVisitor visitor)
140140
);
141141
}
142142

143+
private bool ShouldConvertMemberExpression(Type initialType, Type mappedType)
144+
{
145+
if (initialType.IsLiteralType())
146+
return true;
147+
148+
if (!initialType.IsEnumType())
149+
return false;
150+
151+
if (initialType.IsNullableType())
152+
initialType = Nullable.GetUnderlyingType(initialType);
153+
154+
if (mappedType.IsNullableType())
155+
initialType = Nullable.GetUnderlyingType(mappedType);
156+
157+
return mappedType == Enum.GetUnderlyingType(initialType);
158+
}
159+
143160
protected Expression GetMemberExpressionFromCustomExpression(List<PropertyMapInfo> propertyMapInfoList, PropertyMapInfo lastWithCustExpression, Expression mappedParentExpr)
144161
=> GetMemberExpressionFromCustomExpression
145162
(
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+

2+
using System;
3+
using System.Linq.Expressions;
4+
using Xunit;
5+
6+
namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
7+
{
8+
public class ExpressionMappingEnumToNumericOrString : AutoMapperSpecBase
9+
{
10+
public enum SimpleEnumByte : byte
11+
{
12+
Value1 = 1,
13+
Value2 = 2,
14+
Value3 = 3
15+
}
16+
public enum SimpleEnumSByte : sbyte
17+
{
18+
Value1 = 1,
19+
Value2 = 2,
20+
Value3 = 3
21+
}
22+
public enum SimpleEnumShort : short
23+
{
24+
Value1 = 1,
25+
Value2 = 2,
26+
Value3 = 3
27+
}
28+
public enum SimpleEnumUShort : ushort
29+
{
30+
Value1 = 1,
31+
Value2 = 2,
32+
Value3 = 3
33+
}
34+
public enum SimpleEnumInt : int
35+
{
36+
Value1 = 1,
37+
Value2 = 2,
38+
Value3 = 3
39+
}
40+
public enum SimpleEnumUInt : uint
41+
{
42+
Value1 = 1,
43+
Value2 = 2,
44+
Value3 = 3
45+
}
46+
public enum SimpleEnumLong : long
47+
{
48+
Value1 = 1,
49+
Value2 = 2,
50+
Value3 = long.MaxValue
51+
}
52+
public enum SimpleEnumULong : ulong
53+
{
54+
Value1 = 1,
55+
Value2 = 2,
56+
Value3 = long.MaxValue
57+
}
58+
private class EntityDto<TEnum>
59+
where TEnum : Enum
60+
{
61+
internal TEnum Value { get; set; }
62+
}
63+
private class Entity<T>
64+
{
65+
internal T Value { get; set; }
66+
}
67+
68+
protected override MapperConfiguration Configuration
69+
{
70+
get
71+
{
72+
return new MapperConfiguration(config =>
73+
{
74+
config.AddExpressionMapping();
75+
config.CreateMap<Entity<byte>, EntityDto<SimpleEnumByte>>()
76+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
77+
.ReverseMap();
78+
79+
config.CreateMap<Entity<sbyte>, EntityDto<SimpleEnumSByte>>()
80+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
81+
.ReverseMap();
82+
config.CreateMap<Entity<short>, EntityDto<SimpleEnumShort>>()
83+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
84+
.ReverseMap();
85+
config.CreateMap<Entity<ushort>, EntityDto<SimpleEnumUShort>>()
86+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
87+
.ReverseMap();
88+
config.CreateMap<Entity<int>, EntityDto<SimpleEnumInt>>()
89+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
90+
.ReverseMap();
91+
config.CreateMap<Entity<uint>, EntityDto<SimpleEnumUInt>>()
92+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
93+
.ReverseMap();
94+
config.CreateMap<Entity<long>, EntityDto<SimpleEnumLong>>()
95+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
96+
.ReverseMap();
97+
config.CreateMap<Entity<ulong>, EntityDto<SimpleEnumULong>>()
98+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
99+
.ReverseMap();
100+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumByte>>()
101+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
102+
.ReverseMap();
103+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumSByte>>()
104+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
105+
.ReverseMap();
106+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumShort>>()
107+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
108+
.ReverseMap();
109+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumUShort>>()
110+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
111+
.ReverseMap();
112+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumInt>>()
113+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
114+
.ReverseMap();
115+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumUInt>>()
116+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
117+
.ReverseMap();
118+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumLong>>()
119+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
120+
.ReverseMap();
121+
config.CreateMap<Entity<string>, EntityDto<SimpleEnumULong>>()
122+
.ForMember(dest => dest.Value, config => config.MapFrom(src => src.Value))
123+
.ReverseMap();
124+
125+
config.CreateMap<SimpleEnumByte, string>().ConvertUsing(e => e.ToString());
126+
config.CreateMap<SimpleEnumSByte, string>().ConvertUsing(e => e.ToString());
127+
config.CreateMap<SimpleEnumShort, string>().ConvertUsing(e => e.ToString());
128+
config.CreateMap<SimpleEnumUShort, string>().ConvertUsing(e => e.ToString());
129+
config.CreateMap<SimpleEnumInt, string>().ConvertUsing(e => e.ToString());
130+
config.CreateMap<SimpleEnumUInt, string>().ConvertUsing(e => e.ToString());
131+
config.CreateMap<SimpleEnumLong, string>().ConvertUsing(e => e.ToString());
132+
config.CreateMap<SimpleEnumULong, string>().ConvertUsing(e => e.ToString());
133+
134+
config.CreateMap<ComplexEntity, ComplexEntityDto>()
135+
.ForMember(dest => dest.intToEnum, config => config.MapFrom(src => src.intToEnum))
136+
.ForMember(dest => dest.enumToEnum, config => config.MapFrom(src => src.enumToEnum))
137+
.ForMember(dest => dest.enumToInt, config => config.MapFrom(src => src.enumToInt))
138+
.ForMember(dest => dest.intToInt, config => config.MapFrom(src => src.intToInt))
139+
.ReverseMap();
140+
});
141+
}
142+
}
143+
144+
[Theory]
145+
[InlineData(SimpleEnumByte.Value2, (byte)2)]
146+
[InlineData(SimpleEnumSByte.Value2, (sbyte)2)]
147+
[InlineData(SimpleEnumShort.Value2, (short)2)]
148+
[InlineData(SimpleEnumUShort.Value2, (ushort)2)]
149+
[InlineData(SimpleEnumInt.Value2, 2)]
150+
[InlineData(SimpleEnumUInt.Value2, 2U)]
151+
[InlineData(SimpleEnumLong.Value2, 2L)]
152+
[InlineData(SimpleEnumULong.Value2, 2UL)]
153+
[InlineData(SimpleEnumSByte.Value2, (sbyte)1)]
154+
[InlineData(SimpleEnumByte.Value2, (byte)1)]
155+
[InlineData(SimpleEnumShort.Value2, (short)1)]
156+
[InlineData(SimpleEnumUShort.Value2, (ushort)1)]
157+
[InlineData(SimpleEnumInt.Value2, 1)]
158+
[InlineData(SimpleEnumUInt.Value2, 1U)]
159+
[InlineData(SimpleEnumLong.Value2, 1L)]
160+
[InlineData(SimpleEnumULong.Value2, 1UL)]
161+
[InlineData(SimpleEnumSByte.Value3, (sbyte)3)]
162+
[InlineData(SimpleEnumByte.Value3, (byte)1)]
163+
[InlineData(SimpleEnumShort.Value3, (short)1)]
164+
[InlineData(SimpleEnumUShort.Value3, (ushort)1)]
165+
[InlineData(SimpleEnumInt.Value3, 1)]
166+
[InlineData(SimpleEnumUInt.Value3, 1U)]
167+
[InlineData(SimpleEnumLong.Value3, 1L)]
168+
[InlineData(SimpleEnumULong.Value3, 1UL)]
169+
[InlineData(SimpleEnumSByte.Value3, (sbyte)3)]
170+
[InlineData(SimpleEnumByte.Value3, (byte)3)]
171+
[InlineData(SimpleEnumShort.Value3, (short)3)]
172+
[InlineData(SimpleEnumUShort.Value3, (ushort)3)]
173+
[InlineData(SimpleEnumInt.Value3, 3)]
174+
[InlineData(SimpleEnumUInt.Value3, 3U)]
175+
[InlineData(SimpleEnumLong.Value3, 3L)]
176+
[InlineData(SimpleEnumULong.Value3, 3UL)]
177+
[InlineData(SimpleEnumLong.Value3, long.MaxValue)]
178+
[InlineData(SimpleEnumULong.Value3, (ulong)long.MaxValue)]
179+
public void BinaryExpressionEquals<TEnum, TNumeric>(TEnum enumConstant, TNumeric numericConstant)
180+
where TEnum : Enum
181+
{
182+
var correctResult = ((TNumeric)(object)enumConstant).Equals(numericConstant);
183+
Expression<Func<Entity<TNumeric>, bool>> mappedExpression;
184+
{
185+
var param = Expression.Parameter(typeof(EntityDto<TEnum>), "x");
186+
var property = Expression.Property(param, nameof(EntityDto<TEnum>.Value));
187+
var constantExp = Expression.Constant(enumConstant, typeof(TEnum));
188+
var binaryExpression = Expression.Equal(property, constantExp);
189+
var lambdaExpression = Expression.Lambda(binaryExpression, param);
190+
mappedExpression = Mapper.Map<Expression<Func<Entity<TNumeric>, bool>>>(lambdaExpression);
191+
}
192+
193+
var mappedExpressionDelegate = mappedExpression.Compile();
194+
195+
var entity = new Entity<TNumeric> { Value = numericConstant };
196+
var result = mappedExpressionDelegate(entity);
197+
198+
Assert.Equal(result, correctResult);
199+
}
200+
201+
private class ComplexEntity
202+
{
203+
public int intToEnum { get; set; }
204+
public SimpleEnumInt enumToInt { get; set; }
205+
public SimpleEnumInt enumToEnum { get; set; }
206+
public int intToInt { get; set; }
207+
}
208+
209+
private class ComplexEntityDto
210+
{
211+
public SimpleEnumInt intToEnum { get; set; }
212+
public int enumToInt { get; set; }
213+
214+
public SimpleEnumInt enumToEnum { get; set; }
215+
public int intToInt { get; set; }
216+
}
217+
218+
[Fact]
219+
public void BinaryExpressionPartialTranslation()
220+
{
221+
Expression<Func<ComplexEntityDto, bool>> mappedExpression;
222+
{
223+
var param = Expression.Parameter(typeof(ComplexEntity), "x");
224+
var property1 = Expression.Property(param, nameof(ComplexEntity.intToEnum));
225+
var property2 = Expression.Property(param, nameof(ComplexEntity.intToInt));
226+
var property5 = Expression.Property(param, nameof(ComplexEntity.enumToEnum));
227+
var property6 = Expression.Property(param, nameof(ComplexEntity.enumToInt));
228+
229+
var constant1 = Expression.Constant(2, typeof(int));
230+
var constant2 = Expression.Constant(1, typeof(int));
231+
var constant5 = Expression.Constant(SimpleEnumInt.Value3, typeof(SimpleEnumInt));
232+
var constant6 = Expression.Constant(SimpleEnumInt.Value2, typeof(SimpleEnumInt));
233+
234+
Expression[] equals = new Expression[]{
235+
Expression.Equal(property1, constant1),
236+
Expression.Equal(property2, constant2),
237+
Expression.Equal(property5, constant5),
238+
Expression.Equal(property6, constant6),
239+
};
240+
241+
Expression andExpression = equals[0];
242+
for (int i = 1; i < equals.Length; i++)
243+
{
244+
andExpression = Expression.And(andExpression, equals[i]);
245+
}
246+
var lambdaExpression = Expression.Lambda(andExpression, param);
247+
mappedExpression = Mapper.Map<Expression<Func<ComplexEntityDto, bool>>>(lambdaExpression);
248+
}
249+
250+
Expression<Func<ComplexEntityDto, bool>> translatedExpression =
251+
translatedExpression = x =>
252+
x.intToEnum == SimpleEnumInt.Value2
253+
&& x.intToInt == (int)SimpleEnumInt.Value1
254+
&& x.enumToEnum == SimpleEnumInt.Value3
255+
&& x.enumToInt == (int)SimpleEnumInt.Value2
256+
;
257+
258+
var mappedExpressionDelegate = mappedExpression.Compile();
259+
var translatedExpressionDelegate = translatedExpression.Compile();
260+
261+
var entity = new ComplexEntityDto { intToEnum = SimpleEnumInt.Value2, intToInt = 1, enumToEnum = SimpleEnumInt.Value3, enumToInt = 2 };
262+
var mappedResult = mappedExpressionDelegate(entity);
263+
var translatedResult = translatedExpressionDelegate(entity);
264+
265+
Assert.True(translatedResult);
266+
Assert.Equal(mappedResult, translatedResult);
267+
}
268+
}
269+
}

0 commit comments

Comments
 (0)