Skip to content

Commit 55e17ca

Browse files
committed
Fix for Issue 41 - Argument Exception : Property is not defined when mapping a lambda using properties of child object.
1 parent 0a3888c commit 55e17ca

File tree

7 files changed

+116
-44
lines changed

7 files changed

+116
-44
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Authors>Jimmy Bogard</Authors>
44
<LangVersion>latest</LangVersion>
5-
<VersionPrefix>3.0.1</VersionPrefix>
5+
<VersionPrefix>3.0.2-preview01</VersionPrefix>
66
<WarningsAsErrors>true</WarningsAsErrors>
77
<NoWarn>$(NoWarn);1701;1702;1591</NoWarn>
88
</PropertyGroup>

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,15 @@ public static string GetPropertyFullName(this Expression expression)
5353
//the node represents parameter of the expression
5454
switch (expression.NodeType)
5555
{
56-
case ExpressionType.Parameter:
57-
return string.Empty;
5856
case ExpressionType.MemberAccess:
5957
var memberExpression = (MemberExpression)expression;
6058
var parentFullName = memberExpression.Expression.GetPropertyFullName();
6159
return string.IsNullOrEmpty(parentFullName)
6260
? memberExpression.Member.Name
6361
: string.Concat(memberExpression.Expression.GetPropertyFullName(), period, memberExpression.Member.Name);
62+
default:
63+
return string.Empty;
6464
}
65-
66-
throw new InvalidOperationException(Resource.invalidExpErr);
6765
}
6866

6967
private static MemberExpression GetMemberExpression(LambdaExpression expr)
@@ -135,6 +133,22 @@ public static ParameterExpression GetParameterExpression(this Expression express
135133
return null;
136134
}
137135

136+
/// <summary>
137+
/// Returns the first ancestor node that is not a MemberExpression.
138+
/// </summary>
139+
/// <param name="expression"></param>
140+
/// <returns></returns>
141+
public static Expression GetBaseOfMemberExpression(this MemberExpression expression)
142+
{
143+
switch (expression.Expression.NodeType)
144+
{
145+
case ExpressionType.MemberAccess:
146+
return GetBaseOfMemberExpression((MemberExpression)expression.Expression);
147+
default:
148+
return expression.Expression;
149+
}
150+
}
151+
138152
/// <summary>
139153
/// Adds member expressions to an existing expression.
140154
/// </summary>

src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ namespace AutoMapper.Extensions.ExpressionMapping
1010
{
1111
internal class FindMemberExpressionsVisitor : ExpressionVisitor
1212
{
13-
internal FindMemberExpressionsVisitor(ParameterExpression newParameter) => _newParameter = newParameter;
13+
internal FindMemberExpressionsVisitor(Expression newParentExpression) => _newParentExpression = newParentExpression;
1414

15-
private readonly ParameterExpression _newParameter;
15+
private readonly Expression _newParentExpression;
1616
private readonly List<MemberExpression> _memberExpressions = new List<MemberExpression>();
1717

1818
public MemberExpression Result
@@ -31,21 +31,21 @@ public MemberExpression Result
3131
result = next;
3232
else throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
3333
Resource.includeExpressionTooComplex,
34-
string.Concat(_newParameter.Type.Name, period, result),
35-
string.Concat(_newParameter.Type.Name, period, next)));
34+
string.Concat(_newParentExpression.Type.Name, period, result),
35+
string.Concat(_newParentExpression.Type.Name, period, next)));
3636

3737
return result;
3838
});
3939

40-
return ExpressionHelpers.MemberAccesses(member, _newParameter);
40+
return ExpressionHelpers.MemberAccesses(member, _newParentExpression);
4141
}
4242
}
4343

4444
protected override Expression VisitMember(MemberExpression node)
4545
{
4646
var parameterExpression = node.GetParameterExpression();
4747
var sType = parameterExpression?.Type;
48-
if (sType != null && _newParameter.Type == sType && node.IsMemberExpression())
48+
if (sType != null && _newParentExpression.Type == sType && node.IsMemberExpression())
4949
{
5050
if (node.Expression.NodeType == ExpressionType.MemberAccess && node.Type.IsLiteralType())
5151
_memberExpressions.Add((MemberExpression)node.Expression);

src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,19 @@ public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationPr
1818

1919
protected override Expression VisitMember(MemberExpression node)
2020
{
21-
string sourcePath;
22-
2321
var parameterExpression = node.GetParameterExpression();
2422
if (parameterExpression == null)
2523
return base.VisitMember(node);
2624

2725
InfoDictionary.Add(parameterExpression, TypeMappings);
28-
var sType = parameterExpression.Type;
29-
if (sType != null && InfoDictionary.ContainsKey(parameterExpression) && node.IsMemberExpression())
30-
{
31-
sourcePath = node.GetPropertyFullName();
32-
}
33-
else
34-
{
35-
return base.VisitMember(node);
36-
}
26+
string sourcePath = node.GetPropertyFullName();
27+
Expression baseParentExpr = node.GetBaseOfMemberExpression();
28+
Expression visitedParentExpr = this.Visit(baseParentExpr);
29+
Type sType = baseParentExpr.Type;
30+
Type dType = visitedParentExpr.Type;
3731

3832
var propertyMapInfoList = new List<PropertyMapInfo>();
39-
FindDestinationFullName(sType, InfoDictionary[parameterExpression].DestType, sourcePath, propertyMapInfoList);
33+
FindDestinationFullName(sType, dType, sourcePath, propertyMapInfoList);
4034
string fullName;
4135

4236
if (propertyMapInfoList.Any(x => x.CustomExpression != null))//CustomExpression takes precedence over DestinationPropertyInfo
@@ -59,22 +53,25 @@ protected override Expression VisitMember(MemberExpression node)
5953

6054
fullName = BuildFullName(beforeCustExpression);
6155

62-
var visitor = new PrependParentNameVisitor(last.CustomExpression.Parameters[0].Type/*Parent type of current property*/, fullName, InfoDictionary[parameterExpression].NewParameter);
56+
var visitor = new PrependParentNameVisitor
57+
(
58+
last.CustomExpression.Parameters[0].Type/*Parent type of current property*/,
59+
fullName,
60+
visitedParentExpr
61+
);
6362

6463
var ex = propertyMapInfoList[propertyMapInfoList.Count - 1] != last
6564
? visitor.Visit(last.CustomExpression.Body.MemberAccesses(afterCustExpression))
6665
: visitor.Visit(last.CustomExpression.Body);
6766

68-
var v = new FindMemberExpressionsVisitor(InfoDictionary[parameterExpression].NewParameter);
67+
var v = new FindMemberExpressionsVisitor(visitedParentExpr);
6968
v.Visit(ex);
7069

7170
return v.Result;
7271
}
7372
fullName = BuildFullName(propertyMapInfoList);
7473
var me = ExpressionHelpers.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter);
75-
if (me.Expression.NodeType == ExpressionType.MemberAccess && (me.Type == typeof(string) || me.Type.GetTypeInfo().IsValueType || (me.Type.GetTypeInfo().IsGenericType
76-
&& me.Type.GetGenericTypeDefinition() == typeof(Nullable<>)
77-
&& Nullable.GetUnderlyingType(me.Type).GetTypeInfo().IsValueType)))
74+
if (me.Expression.NodeType == ExpressionType.MemberAccess && me.Type.IsLiteralType())
7875
{
7976
return me.Expression;
8077
}

src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace AutoMapper.Extensions.ExpressionMapping
77
{
88
internal class PrependParentNameVisitor : ExpressionVisitor
99
{
10-
public PrependParentNameVisitor(Type currentParameterType, string parentFullName, ParameterExpression newParameter)
10+
public PrependParentNameVisitor(Type currentParameterType, string parentFullName, Expression newParameter)
1111
{
1212
CurrentParameterType = currentParameterType;
1313
ParentFullName = parentFullName;
@@ -16,7 +16,7 @@ public PrependParentNameVisitor(Type currentParameterType, string parentFullName
1616

1717
public Type CurrentParameterType { get; }
1818
public string ParentFullName { get; }
19-
public ParameterExpression NewParameter { get; }
19+
public Expression NewParameter { get; }
2020

2121
protected override Expression VisitMember(MemberExpression node)
2222
{

src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,20 @@ protected override Expression VisitParameter(ParameterExpression node)
4040

4141
protected override Expression VisitMember(MemberExpression node)
4242
{
43-
string sourcePath;
44-
4543
var parameterExpression = node.GetParameterExpression();
4644
if (parameterExpression == null)
4745
return base.VisitMember(node);
4846

4947
InfoDictionary.Add(parameterExpression, TypeMappings);
5048

51-
var sType = parameterExpression.Type;
52-
if (InfoDictionary.ContainsKey(parameterExpression) && node.IsMemberExpression())
53-
{
54-
sourcePath = node.GetPropertyFullName();
55-
}
56-
else
57-
{
58-
return base.VisitMember(node);
59-
}
49+
string sourcePath = node.GetPropertyFullName();
50+
Expression baseParentExpr = node.GetBaseOfMemberExpression();
51+
Expression visitedParentExpr = this.Visit(baseParentExpr);
52+
Type sType = baseParentExpr.Type;
53+
Type dType = visitedParentExpr.Type;
6054

6155
var propertyMapInfoList = new List<PropertyMapInfo>();
62-
FindDestinationFullName(sType, InfoDictionary[parameterExpression].DestType, sourcePath, propertyMapInfoList);
56+
FindDestinationFullName(sType, dType, sourcePath, propertyMapInfoList);
6357
string fullName;
6458

6559
if (propertyMapInfoList.Any(x => x.CustomExpression != null))
@@ -80,7 +74,12 @@ protected override Expression VisitMember(MemberExpression node)
8074
});
8175

8276
fullName = BuildFullName(beforeCustExpression);
83-
var visitor = new PrependParentNameVisitor(last.CustomExpression.Parameters[0].Type/*Parent type of current property*/, fullName, InfoDictionary[parameterExpression].NewParameter);
77+
var visitor = new PrependParentNameVisitor
78+
(
79+
last.CustomExpression.Parameters[0].Type/*Parent type of current property*/,
80+
fullName,
81+
visitedParentExpr
82+
);
8483

8584
var ex = propertyMapInfoList[propertyMapInfoList.Count - 1] != last
8685
? visitor.Visit(last.CustomExpression.Body.MemberAccesses(afterCustExpression))
@@ -90,7 +89,7 @@ protected override Expression VisitMember(MemberExpression node)
9089
return ex;
9190
}
9291
fullName = BuildFullName(propertyMapInfoList);
93-
var me = ExpressionHelpers.MemberAccesses(fullName, InfoDictionary[parameterExpression].NewParameter);
92+
var me = ExpressionHelpers.MemberAccesses(fullName, visitedParentExpr);
9493

9594
this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, me.Type);
9695
return me;

tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,35 @@ public void Can_map_expression_when_mapped_properties_have_a_different_generic_a
692692
//Assert
693693
Assert.NotNull(dest);
694694
}
695+
696+
[Fact]
697+
public void Can_map_expression_when_mapped_when_members_parent_is_a_method()
698+
{
699+
//Arrange
700+
List<EmployeeEntity> empEntity = new List<EmployeeEntity>
701+
{
702+
new EmployeeEntity { Id = 1, Name = "Jean-Louis", Age = 39, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
703+
new EmployeeEntity { Id = 2, Name = "Jean-Paul", Age = 32, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) } }.ToList() },
704+
new EmployeeEntity { Id = 3, Name = "Jean-Christophe", Age = 19, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
705+
new EmployeeEntity { Id = 4, Name = "Jean-Marie", Age = 27, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-3) } }.ToList() },
706+
new EmployeeEntity { Id = 5, Name = "Jean-Marc", Age = 22, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) } }.ToList() },
707+
new EmployeeEntity { Id = 5, Name = "Jean-Pierre", Age = 22, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) } }.ToList() },
708+
new EmployeeEntity { Id = 6, Name = "Christophe", Age = 55, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
709+
new EmployeeEntity { Id = 7, Name = "Marc", Age = 23, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) } }.ToList() },
710+
new EmployeeEntity { Id = 8, Name = "Paul", Age = 38, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new EventEntity { EventType = "Stop", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
711+
new EmployeeEntity { Id = 9, Name = "Jean", Age = 32, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new EventEntity { EventType = "Stop", EventDate = DateTime.Today.AddYears(-2) } }.ToList() },
712+
};
713+
Expression<Func<EmployeeModel, bool>> filter = emp =>
714+
emp.Events.Any(e => e.EventType.Equals("Stop")) &&
715+
emp.Events.First(e => e.EventType.Equals("Stop")).EventDate < DateTime.Today.AddYears(-1);
716+
717+
//Act
718+
Expression<Func<EmployeeEntity, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EmployeeEntity, bool>>>(filter);
719+
List<EmployeeEntity> res = empEntity.AsQueryable().Where(mappedFilter).ToList();
720+
721+
//Assert
722+
Assert.True(res.Count == 1);
723+
}
695724
#endregion Tests
696725

697726
private static void SetupAutoMapper()
@@ -1096,6 +1125,35 @@ class ListExtension : List<string>
10961125
{
10971126
}
10981127

1128+
internal class EmployeeEntity
1129+
{
1130+
public int Id { get; set; }
1131+
public string Name { get; set; }
1132+
public int Age { get; set; }
1133+
public List<EventEntity> Events { get; set; }
1134+
}
1135+
1136+
internal class EventEntity
1137+
{
1138+
public string EventType { get; set; }
1139+
public DateTime EventDate { get; set; }
1140+
}
1141+
1142+
1143+
internal class EmployeeModel
1144+
{
1145+
public int Id { get; set; }
1146+
public string Name { get; set; }
1147+
public int Age { get; set; }
1148+
public List<EventModel> Events { get; set; }
1149+
}
1150+
1151+
internal class EventModel
1152+
{
1153+
public string EventType { get; set; }
1154+
public DateTime EventDate { get; set; }
1155+
}
1156+
10991157
public class OrganizationProfile : Profile
11001158
{
11011159
public OrganizationProfile()
@@ -1198,6 +1256,10 @@ public OrganizationProfile()
11981256
.ForMember(d => d.Count, opt => opt.MapFrom(s => s.Count));
11991257

12001258
CreateMap<Branch, BranchModel>();
1259+
1260+
CreateMap<EmployeeModel, EmployeeEntity>().ReverseMap();
1261+
CreateMap<EventModel, EventEntity>().ReverseMap();
1262+
//CreateMap<EventEntity, EmployeeModel>();
12011263
}
12021264
}
12031265

0 commit comments

Comments
 (0)