Skip to content

Commit aece882

Browse files
authored
Merge pull request #127 from AutoMapper/Issue126
Fixes Issue #126.
2 parents d580772 + e9aff7c commit aece882

File tree

3 files changed

+312
-5
lines changed

3 files changed

+312
-5
lines changed

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,21 @@ public static ParameterExpression GetParameterExpression(this Expression express
123123
return GetParameterExpression(ue?.Operand);
124124
case ExpressionType.TypeAs:
125125
return ((UnaryExpression)expression).Operand.GetParameterExpression();
126+
case ExpressionType.TypeIs:
127+
return ((TypeBinaryExpression)expression).Expression.GetParameterExpression();
126128
case ExpressionType.MemberAccess:
127129
return GetParameterExpression(((MemberExpression)expression).Expression);
128130
case ExpressionType.Call:
129131
var methodExpression = expression as MethodCallExpression;
130-
var memberExpression = methodExpression?.Object as MemberExpression;//Method is an instance method
132+
var parentExpression = methodExpression?.Object;//Method is an instance method
131133

132134
var isExtension = methodExpression != null && methodExpression.Method.IsDefined(typeof(ExtensionAttribute), true);
133-
if (isExtension && memberExpression == null && methodExpression.Arguments.Count > 0)
134-
memberExpression = methodExpression.Arguments[0] as MemberExpression;//Method is an extension method based on the type of methodExpression.Arguments[0] and methodExpression.Arguments[0] is a member expression.
135+
if (isExtension && parentExpression == null && methodExpression.Arguments.Count > 0)
136+
parentExpression = methodExpression.Arguments[0];//Method is an extension method based on the type of methodExpression.Arguments[0].
135137

136-
return isExtension && memberExpression == null && methodExpression.Arguments.Count > 0
138+
return isExtension && parentExpression == null && methodExpression.Arguments.Count > 0
137139
? GetParameterExpression(methodExpression.Arguments[0])
138-
: (memberExpression == null ? null : GetParameterExpression(memberExpression.Expression));
140+
: GetParameterExpression(parentExpression);
139141
}
140142

141143
return null;

src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using AutoMapper.Extensions.ExpressionMapping.Extensions;
2+
using System.Linq;
23
using System.Linq.Expressions;
4+
using System.Runtime.CompilerServices;
35

46
namespace AutoMapper.Extensions.ExpressionMapping
57
{
@@ -16,6 +18,23 @@ public PrependParentNameVisitor(ParameterExpression currentParameter, string par
1618
public string ParentFullName { get; }
1719
public Expression NewParameter { get; }
1820

21+
protected override Expression VisitTypeBinary(TypeBinaryExpression node)
22+
{
23+
if (!(node.Expression is ParameterExpression))
24+
return base.VisitTypeBinary(node);
25+
26+
if (!object.ReferenceEquals(CurrentParameter, node.GetParameterExpression()))
27+
return base.VisitTypeBinary(node);
28+
29+
return Expression.TypeIs
30+
(
31+
string.IsNullOrEmpty(ParentFullName)
32+
? NewParameter
33+
: ExpressionHelpers.MemberAccesses(ParentFullName, NewParameter),
34+
node.TypeOperand
35+
);
36+
}
37+
1938
protected override Expression VisitMember(MemberExpression node)
2039
{
2140
if (node.NodeType == ExpressionType.Constant)
@@ -32,5 +51,78 @@ protected override Expression VisitMember(MemberExpression node)
3251
NewParameter
3352
);
3453
}
54+
55+
protected override Expression VisitMethodCall(MethodCallExpression node)
56+
{
57+
if (!IsParentParameterExpression())
58+
return base.VisitMethodCall(node);
59+
60+
if (!object.ReferenceEquals(CurrentParameter, node.GetParameterExpression()))
61+
return base.VisitMethodCall(node);
62+
63+
if (node.Method.IsStatic)
64+
{
65+
if (!IsExtentionMethod())
66+
return base.VisitMethodCall(node);
67+
68+
if (node.Method.IsGenericMethod)
69+
return Expression.Call
70+
(
71+
node.Method.DeclaringType,
72+
node.Method.Name,
73+
node.Method.GetGenericArguments(),
74+
GetNewArgumentsForExtensionMethod()
75+
);
76+
else
77+
return Expression.Call(node.Method, GetNewArgumentsForExtensionMethod());
78+
}
79+
80+
//instance method
81+
if (node.Method.IsGenericMethod)
82+
{
83+
return Expression.Call
84+
(
85+
GetNewParent(),
86+
node.Method.Name,
87+
node.Method.GetGenericArguments(),
88+
node.Arguments.ToArray()
89+
);
90+
}
91+
else
92+
{
93+
return Expression.Call
94+
(
95+
GetNewParent(),
96+
node.Method,
97+
node.Arguments
98+
);
99+
}
100+
101+
Expression[] GetNewArgumentsForExtensionMethod()
102+
{
103+
Expression[] arguments = node.Arguments.ToArray();
104+
arguments[0] = GetNewParent();
105+
return arguments.ToArray();
106+
}
107+
108+
Expression GetNewParent()
109+
=> string.IsNullOrEmpty(ParentFullName)
110+
? NewParameter
111+
: ExpressionHelpers.MemberAccesses(ParentFullName, NewParameter);
112+
113+
bool IsParentParameterExpression()
114+
{
115+
if (node.Method.IsStatic)
116+
return node.Arguments[0] is ParameterExpression;
117+
118+
if (!node.Method.IsStatic)
119+
return node.Object is ParameterExpression;
120+
121+
return false;
122+
}
123+
124+
bool IsExtentionMethod()
125+
=> node.Method.IsDefined(typeof(ExtensionAttribute), true);
126+
}
35127
}
36128
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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 CanMapMemberFromTypeBinaryExpression
10+
{
11+
[Fact]
12+
public void Can_map_using_type_binary_expression_to_test_the_parameter_expression()
13+
{
14+
//arrange
15+
var config = new MapperConfiguration(cfg =>
16+
{
17+
cfg.AddExpressionMapping();
18+
19+
cfg.CreateMap<Shape, ShapeDto>()
20+
.ForMember(dto => dto.ShapeType, o => o.MapFrom(s => s is Triangle ? ShapeType.Triangle : s is Circle ? ShapeType.Circle : ShapeType.Unknown))
21+
.ForMember(dto => dto.DynamicProperties, o => o.Ignore());
22+
});
23+
24+
var mapper = config.CreateMapper();
25+
Expression<Func<ShapeDto, bool>> where = x => x.ShapeType == ShapeType.Circle;
26+
27+
//act
28+
Expression<Func<Shape, bool>> whereMapped = mapper.MapExpression<Expression<Func<Shape, bool>>>(where);
29+
var list = new List<Shape> { new Circle(), new Triangle() }.AsQueryable().Where(whereMapped).ToList();
30+
31+
//assert
32+
Assert.Single(list);
33+
Assert.Equal
34+
(
35+
"x => (Convert(IIF((x Is Triangle), Triangle, IIF((x Is Circle), Circle, Unknown)), Int32) == 2)",
36+
whereMapped.ToString()
37+
);
38+
}
39+
40+
[Fact]
41+
public void Can_map_using_type_binary_expression_to_test_a_member_expression()
42+
{
43+
//arrange
44+
var config = new MapperConfiguration(cfg =>
45+
{
46+
cfg.AddExpressionMapping();
47+
48+
cfg.CreateMap<ShapeHolder, ShapeHolderDto>();
49+
cfg.CreateMap<Shape, ShapeDto>()
50+
.ForMember(dto => dto.ShapeType, o => o.MapFrom(s => s is Triangle ? ShapeType.Triangle : s is Circle ? ShapeType.Circle : ShapeType.Unknown))
51+
.ForMember(dto => dto.DynamicProperties, o => o.Ignore());
52+
});
53+
54+
var mapper = config.CreateMapper();
55+
Expression<Func<ShapeHolderDto, bool>> where = x => x.Shape.ShapeType == ShapeType.Circle;
56+
57+
//act
58+
Expression<Func<ShapeHolder, bool>> whereMapped = mapper.MapExpression<Expression<Func<ShapeHolder, bool>>>(where);
59+
var list = new List<ShapeHolder>
60+
{
61+
new ShapeHolder { Shape = new Circle() },
62+
new ShapeHolder { Shape = new Triangle() }
63+
}
64+
.AsQueryable()
65+
.Where(whereMapped).ToList();
66+
67+
//assert
68+
Assert.Single(list);
69+
Assert.Equal
70+
(
71+
"x => (Convert(IIF((x.Shape Is Triangle), Triangle, IIF((x.Shape Is Circle), Circle, Unknown)), Int32) == 2)",
72+
whereMapped.ToString()
73+
);
74+
}
75+
76+
[Fact]
77+
public void Can_map_using_instance_method_call_to_test_the_parameter_expression()
78+
{
79+
//arrange
80+
var config = new MapperConfiguration(cfg =>
81+
{
82+
cfg.AddExpressionMapping();
83+
84+
cfg.CreateMap<Shape, ShapeDto>()
85+
.ForMember(dto => dto.ShapeType, o => o.MapFrom(s => s.GetType() == typeof(Triangle) ? ShapeType.Triangle : s.GetType() == typeof(Circle) ? ShapeType.Circle : ShapeType.Unknown))
86+
.ForMember(dto => dto.DynamicProperties, o => o.Ignore());
87+
});
88+
89+
var mapper = config.CreateMapper();
90+
Expression<Func<ShapeDto, bool>> where = x => x.ShapeType == ShapeType.Circle;
91+
92+
//act
93+
Expression<Func<Shape, bool>> whereMapped = mapper.MapExpression<Expression<Func<Shape, bool>>>(where);
94+
var list = new List<Shape> { new Circle(), new Triangle() }.AsQueryable().Where(whereMapped).ToList();
95+
96+
//assert
97+
Assert.Single(list);
98+
}
99+
100+
[Fact]
101+
public void Can_map_using_static_method_call_to_test_the_parameter_expression()
102+
{
103+
//arrange
104+
var config = new MapperConfiguration(cfg =>
105+
{
106+
cfg.AddExpressionMapping();
107+
108+
cfg.CreateMap<Shape, ShapeDto>()
109+
.ForMember(dto => dto.HasMany, o => o.MapFrom(s => s.HasMessages()));
110+
});
111+
112+
var mapper = config.CreateMapper();
113+
Expression<Func<ShapeDto, bool>> where = x => x.HasMany;
114+
115+
//act
116+
Expression<Func<Shape, bool>> whereMapped = mapper.MapExpression<Expression<Func<Shape, bool>>>(where);
117+
var list = new List<Shape> { new Circle() { Messages = new List<string> { "" } }, new Triangle() { Messages = new List<string>() } }.AsQueryable().Where(whereMapped).ToList();
118+
119+
//assert
120+
Assert.Single(list);
121+
Assert.Equal
122+
(
123+
"x => x.HasMessages()",
124+
whereMapped.ToString()
125+
);
126+
}
127+
128+
[Fact]
129+
public void Can_map_using_static_generic_method_call_to_test_the_parameter_expression()
130+
{
131+
//arrange
132+
var config = new MapperConfiguration(cfg =>
133+
{
134+
cfg.AddExpressionMapping();
135+
136+
cfg.CreateMap<Shape, ShapeDto>()
137+
.ForMember(dto => dto.IsCircle, o => o.MapFrom(s => s.IsCircle<Shape>()));
138+
});
139+
140+
var mapper = config.CreateMapper();
141+
Expression<Func<ShapeDto, bool>> where = x => x.IsCircle;
142+
143+
//act
144+
Expression<Func<Shape, bool>> whereMapped = mapper.MapExpression<Expression<Func<Shape, bool>>>(where);
145+
var list = new List<Shape> { new Circle(), new Triangle()}.AsQueryable().Where(whereMapped).ToList();
146+
147+
//assert
148+
Assert.Single(list);
149+
Assert.Equal
150+
(
151+
"x => x.IsCircle()",
152+
whereMapped.ToString()
153+
);
154+
}
155+
156+
public class ShapeHolder
157+
{
158+
public Shape Shape { get; set; }
159+
}
160+
161+
public class Shape
162+
{
163+
public string Name { get; set; }
164+
public List<string> Messages { get; set; }
165+
}
166+
167+
public class Triangle : Shape
168+
{
169+
public double A { get; set; }
170+
public double B { get; set; }
171+
public double C { get; set; }
172+
}
173+
174+
public class Circle : Shape
175+
{
176+
public double R { get; set; }
177+
}
178+
179+
public enum ShapeType
180+
{
181+
Unknown,
182+
Triangle,
183+
Circle,
184+
}
185+
186+
public class ShapeDto
187+
{
188+
public string Name { get; set; }
189+
public IDictionary<string, object> DynamicProperties { get; set; }
190+
public ShapeType ShapeType { get; set; }
191+
public bool HasMany { get; set; }
192+
public bool IsCircle { get; set; }
193+
}
194+
195+
public class ShapeHolderDto
196+
{
197+
public ShapeDto Shape { get; set; }
198+
}
199+
}
200+
201+
public static class ShapeExtentions
202+
{
203+
public static bool HasMessages(this CanMapMemberFromTypeBinaryExpression.Shape shape)
204+
{
205+
return shape.Messages.Any();
206+
}
207+
208+
public static bool IsCircle<T>(this T shape)
209+
{
210+
return shape.GetType() == typeof(CanMapMemberFromTypeBinaryExpression.Circle);
211+
}
212+
}
213+
}

0 commit comments

Comments
 (0)