Skip to content

Commit dee8f94

Browse files
authored
Updating the list of literals which may need help to resolve "operator is not defined for the types" exception. (#142)
1 parent f86b6e2 commit dee8f94

File tree

3 files changed

+286
-6
lines changed

3 files changed

+286
-6
lines changed

src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs

+10-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33
using System.Collections.Generic;
44
using System.Linq;
55
using System.Reflection;
6-
using System.Reflection.Emit;
76

87
namespace AutoMapper
98
{
10-
#if NET45
11-
using System.Reflection.Emit;
12-
#endif
139

1410
internal static class TypeExtensions
1511
{
@@ -127,14 +123,23 @@ public static bool IsLiteralType(this Type type)
127123
if (type.IsNullableType())
128124
type = Nullable.GetUnderlyingType(type);
129125

130-
return LiteralTypes.Contains(type);
126+
return LiteralTypes.Contains(type) || NonNetStandardLiteralTypes.Contains(type.FullName);
131127
}
132128

133129
private static HashSet<Type> LiteralTypes => new HashSet<Type>(_literalTypes);
134130

131+
private static readonly HashSet<string> NonNetStandardLiteralTypes = new()
132+
{
133+
"System.DateOnly",
134+
"Microsoft.OData.Edm.Date",
135+
"System.TimeOnly",
136+
"Microsoft.OData.Edm.TimeOfDay"
137+
};
138+
135139
private static Type[] _literalTypes => new Type[] {
136140
typeof(bool),
137141
typeof(DateTime),
142+
typeof(DateTimeOffset),
138143
typeof(TimeSpan),
139144
typeof(Guid),
140145
typeof(decimal),

tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp3.1</TargetFramework>
4+
<TargetFramework>net6.0</TargetFramework>
55

66
<IsPackable>false</IsPackable>
77
</PropertyGroup>
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
11+
<PackageReference Include="Microsoft.OData.Edm" Version="7.10.0" />
1112
<PackageReference Include="Shouldly" Version="3.0.2" />
1213
<PackageReference Include="xunit" Version="2.4.1" />
1314
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
using Microsoft.OData.Edm;
2+
using System;
3+
using System.Linq.Expressions;
4+
using Xunit;
5+
6+
namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
7+
{
8+
public class ShouldThrowInvalidOperationExceptionForUnmatchedLiterals
9+
{
10+
[Theory]
11+
[InlineData(nameof(ProductModel.Bool), typeof(bool?))]
12+
[InlineData(nameof(ProductModel.DateTime), typeof(DateTime?))]
13+
[InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset?))]
14+
[InlineData(nameof(ProductModel.Date), typeof(Date?))]
15+
[InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly?))]
16+
[InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan?))]
17+
[InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay?))]
18+
[InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly?))]
19+
[InlineData(nameof(ProductModel.Guid), typeof(Guid?))]
20+
[InlineData(nameof(ProductModel.Decimal), typeof(decimal?))]
21+
[InlineData(nameof(ProductModel.Byte), typeof(byte?))]
22+
[InlineData(nameof(ProductModel.Short), typeof(short?))]
23+
[InlineData(nameof(ProductModel.Int), typeof(int?))]
24+
[InlineData(nameof(ProductModel.Long), typeof(long?))]
25+
[InlineData(nameof(ProductModel.Float), typeof(float?))]
26+
[InlineData(nameof(ProductModel.Double), typeof(double?))]
27+
[InlineData(nameof(ProductModel.Char), typeof(char?))]
28+
[InlineData(nameof(ProductModel.SByte), typeof(sbyte?))]
29+
[InlineData(nameof(ProductModel.UShort), typeof(ushort?))]
30+
[InlineData(nameof(ProductModel.ULong), typeof(ulong?))]
31+
public void ThrowsCreatingBinaryExpressionCombiningNonNullableParameterWithNullableConstant(string memberName, Type constantType)
32+
{
33+
var mapper = GetMapper();
34+
ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
35+
MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
36+
37+
Assert.Throws<InvalidOperationException>
38+
(
39+
() => Expression.Lambda<Func<ProductModel, bool>>
40+
(
41+
Expression.Equal
42+
(
43+
property,
44+
Expression.Constant(Activator.CreateInstance(constantType), constantType)
45+
),
46+
productParam
47+
)
48+
);
49+
}
50+
51+
[Theory]
52+
[InlineData(nameof(Product.Bool), typeof(bool))]
53+
[InlineData(nameof(Product.DateTime), typeof(DateTime))]
54+
[InlineData(nameof(Product.DateTimeOffset), typeof(DateTimeOffset))]
55+
[InlineData(nameof(Product.Date), typeof(Date))]
56+
[InlineData(nameof(Product.DateOnly), typeof(DateOnly))]
57+
[InlineData(nameof(Product.TimeSpan), typeof(TimeSpan))]
58+
[InlineData(nameof(Product.TimeOfDay), typeof(TimeOfDay))]
59+
[InlineData(nameof(Product.TimeOnly), typeof(TimeOnly))]
60+
[InlineData(nameof(Product.Guid), typeof(Guid))]
61+
[InlineData(nameof(Product.Decimal), typeof(decimal))]
62+
[InlineData(nameof(Product.Byte), typeof(byte))]
63+
[InlineData(nameof(Product.Short), typeof(short))]
64+
[InlineData(nameof(Product.Int), typeof(int))]
65+
[InlineData(nameof(Product.Long), typeof(long))]
66+
[InlineData(nameof(Product.Float), typeof(float))]
67+
[InlineData(nameof(Product.Double), typeof(double))]
68+
[InlineData(nameof(Product.Char), typeof(char))]
69+
[InlineData(nameof(Product.SByte), typeof(sbyte))]
70+
[InlineData(nameof(Product.UShort), typeof(ushort))]
71+
[InlineData(nameof(Product.ULong), typeof(ulong))]
72+
public void ThrowsCreatingBinaryExpressionCombiningNullableParameterWithNonNullableConstant(string memberName, Type constantType)
73+
{
74+
var mapper = GetMapper();
75+
ParameterExpression productParam = Expression.Parameter(typeof(Product), "x");
76+
MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(Product), memberName));
77+
78+
var ex = Assert.Throws<InvalidOperationException>
79+
(
80+
() => Expression.Lambda<Func<Product, bool>>
81+
(
82+
Expression.Equal
83+
(
84+
property,
85+
Expression.Constant(Activator.CreateInstance(constantType), constantType)
86+
),
87+
productParam
88+
)
89+
);
90+
}
91+
92+
[Theory]
93+
[InlineData(nameof(ProductModel.Bool), typeof(bool))]
94+
[InlineData(nameof(ProductModel.DateTime), typeof(DateTime))]
95+
[InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset))]
96+
[InlineData(nameof(ProductModel.Date), typeof(Date))]
97+
[InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly))]
98+
[InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan))]
99+
[InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay))]
100+
[InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly))]
101+
[InlineData(nameof(ProductModel.Guid), typeof(Guid))]
102+
[InlineData(nameof(ProductModel.Decimal), typeof(decimal))]
103+
[InlineData(nameof(ProductModel.Byte), typeof(byte))]
104+
[InlineData(nameof(ProductModel.Short), typeof(short))]
105+
[InlineData(nameof(ProductModel.Int), typeof(int))]
106+
[InlineData(nameof(ProductModel.Long), typeof(long))]
107+
[InlineData(nameof(ProductModel.Float), typeof(float))]
108+
[InlineData(nameof(ProductModel.Double), typeof(double))]
109+
[InlineData(nameof(ProductModel.Char), typeof(char))]
110+
[InlineData(nameof(ProductModel.SByte), typeof(sbyte))]
111+
[InlineData(nameof(ProductModel.UShort), typeof(ushort))]
112+
[InlineData(nameof(ProductModel.ULong), typeof(ulong))]
113+
public void ThrowsmappingExpressionWithMismatchedOperands(string memberName, Type constantType)
114+
{
115+
var mapper = GetMapper();
116+
ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
117+
MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
118+
119+
Expression<Func<ProductModel, bool>> expression = Expression.Lambda<Func<ProductModel, bool>>
120+
(
121+
Expression.Equal
122+
(
123+
property,
124+
Expression.Constant(Activator.CreateInstance(constantType), constantType)
125+
),
126+
productParam
127+
);
128+
129+
var exception = Assert.Throws<InvalidOperationException>
130+
(
131+
() => mapper.MapExpression<Expression<Func<Product, bool>>>(expression)
132+
);
133+
134+
Assert.StartsWith
135+
(
136+
"The source and destination types must be the same for expression mapping between literal types.",
137+
exception.Message
138+
);
139+
}
140+
141+
[Theory]
142+
[InlineData(nameof(ProductModel.Bool), typeof(bool))]
143+
[InlineData(nameof(ProductModel.DateTime), typeof(DateTime))]
144+
[InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset))]
145+
[InlineData(nameof(ProductModel.Date), typeof(Date))]
146+
[InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly))]
147+
[InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan))]
148+
[InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay))]
149+
[InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly))]
150+
[InlineData(nameof(ProductModel.Guid), typeof(Guid))]
151+
[InlineData(nameof(ProductModel.Decimal), typeof(decimal))]
152+
[InlineData(nameof(ProductModel.Byte), typeof(byte))]
153+
[InlineData(nameof(ProductModel.Short), typeof(short))]
154+
[InlineData(nameof(ProductModel.Int), typeof(int))]
155+
[InlineData(nameof(ProductModel.Long), typeof(long))]
156+
[InlineData(nameof(ProductModel.Float), typeof(float))]
157+
[InlineData(nameof(ProductModel.Double), typeof(double))]
158+
[InlineData(nameof(ProductModel.Char), typeof(char))]
159+
[InlineData(nameof(ProductModel.SByte), typeof(sbyte))]
160+
[InlineData(nameof(ProductModel.UShort), typeof(ushort))]
161+
[InlineData(nameof(ProductModel.ULong), typeof(ulong))]
162+
public void MappingExpressionWorksUsingCustomExpressionToResolveBinaryOperators(string memberName, Type constantType)
163+
{
164+
var mapper = GetMapperWithCustomExpressions();
165+
ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
166+
MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
167+
168+
Expression<Func<ProductModel, bool>> expression = Expression.Lambda<Func<ProductModel, bool>>
169+
(
170+
Expression.Equal
171+
(
172+
property,
173+
Expression.Constant(Activator.CreateInstance(constantType), constantType)
174+
),
175+
productParam
176+
);
177+
178+
var mappedExpression = mapper.MapExpression<Expression<Func<Product, bool>>>(expression);
179+
Assert.NotNull(mappedExpression);
180+
}
181+
182+
private static IMapper GetMapper()
183+
{
184+
var config = new MapperConfiguration(c =>
185+
{
186+
c.CreateMap<Product, ProductModel>();
187+
});
188+
config.AssertConfigurationIsValid();
189+
return config.CreateMapper();
190+
}
191+
192+
private static IMapper GetMapperWithCustomExpressions()
193+
{
194+
var config = new MapperConfiguration(c =>
195+
{
196+
c.CreateMap<Product, ProductModel>()
197+
.ForMember(d => d.Bool, o => o.MapFrom(s => s.Bool.Value))
198+
.ForMember(d => d.DateTime, o => o.MapFrom(s => s.DateTime.Value))
199+
.ForMember(d => d.DateTimeOffset, o => o.MapFrom(s => s.DateTimeOffset.Value))
200+
.ForMember(d => d.Date, o => o.MapFrom(s => s.Date.Value))
201+
.ForMember(d => d.DateOnly, o => o.MapFrom(s => s.DateOnly.Value))
202+
.ForMember(d => d.TimeSpan, o => o.MapFrom(s => s.TimeSpan.Value))
203+
.ForMember(d => d.TimeOfDay, o => o.MapFrom(s => s.TimeOfDay.Value))
204+
.ForMember(d => d.TimeOnly, o => o.MapFrom(s => s.TimeOnly.Value))
205+
.ForMember(d => d.Guid, o => o.MapFrom(s => s.Guid.Value))
206+
.ForMember(d => d.Decimal, o => o.MapFrom(s => s.Decimal.Value))
207+
.ForMember(d => d.Byte, o => o.MapFrom(s => s.Byte.Value))
208+
.ForMember(d => d.Short, o => o.MapFrom(s => s.Short.Value))
209+
.ForMember(d => d.Int, o => o.MapFrom(s => s.Int.Value))
210+
.ForMember(d => d.Long, o => o.MapFrom(s => s.Long.Value))
211+
.ForMember(d => d.Float, o => o.MapFrom(s => s.Float.Value))
212+
.ForMember(d => d.Double, o => o.MapFrom(s => s.Double.Value))
213+
.ForMember(d => d.Char, o => o.MapFrom(s => s.Char.Value))
214+
.ForMember(d => d.SByte, o => o.MapFrom(s => s.SByte.Value))
215+
.ForMember(d => d.UShort, o => o.MapFrom(s => s.UShort.Value))
216+
.ForMember(d => d.UInt, o => o.MapFrom(s => s.UInt.Value))
217+
.ForMember(d => d.ULong, o => o.MapFrom(s => s.ULong.Value));
218+
});
219+
220+
config.AssertConfigurationIsValid();
221+
return config.CreateMapper();
222+
}
223+
224+
class Product
225+
{
226+
public bool? Bool { get; set; }
227+
public DateTimeOffset? DateTimeOffset { get; set; }
228+
public DateTime? DateTime { get; set; }
229+
public Date? Date { get; set; }
230+
public DateOnly? DateOnly { get; set; }
231+
public TimeSpan? TimeSpan { get; set; }
232+
public TimeOfDay? TimeOfDay { get; set; }
233+
public TimeOnly? TimeOnly { get; set; }
234+
public Guid? Guid { get; set; }
235+
public decimal? Decimal { get; set; }
236+
public byte? Byte { get; set; }
237+
public short? Short { get; set; }
238+
public int? Int { get; set; }
239+
public long? Long { get; set; }
240+
public float? Float { get; set; }
241+
public double? Double { get; set; }
242+
public char? Char { get; set; }
243+
public sbyte? SByte { get; set; }
244+
public ushort? UShort { get; set; }
245+
public uint? UInt { get; set; }
246+
public ulong? ULong { get; set; }
247+
}
248+
249+
class ProductModel
250+
{
251+
public bool Bool { get; set; }
252+
public DateTimeOffset DateTimeOffset { get; set; }
253+
public DateTime DateTime { get; set; }
254+
public Date Date { get; set; }
255+
public DateOnly DateOnly { get; set; }
256+
public TimeSpan TimeSpan { get; set; }
257+
public TimeOfDay TimeOfDay { get; set; }
258+
public TimeOnly TimeOnly { get; set; }
259+
public Guid Guid { get; set; }
260+
public decimal Decimal { get; set; }
261+
public byte Byte { get; set; }
262+
public short Short { get; set; }
263+
public int Int { get; set; }
264+
public long Long { get; set; }
265+
public float Float { get; set; }
266+
public double Double { get; set; }
267+
public char Char { get; set; }
268+
public sbyte SByte { get; set; }
269+
public ushort UShort { get; set; }
270+
public uint UInt { get; set; }
271+
public ulong ULong { get; set; }
272+
}
273+
}
274+
}

0 commit comments

Comments
 (0)