Skip to content

Commit 7368ae6

Browse files
committed
Mapping complex type target members to null when they have no matching source member and no data sources for any child members
1 parent 9b22ad4 commit 7368ae6

File tree

9 files changed

+99
-14
lines changed

9 files changed

+99
-14
lines changed

AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ public void ShouldConditionallyApplyAConfiguredConstant()
7575
}
7676
}
7777

78+
[Fact]
79+
public void ShouldApplyAConfiguredConstantToANestedMember()
80+
{
81+
using (var mapper = Mapper.CreateNew())
82+
{
83+
mapper.WhenMapping
84+
.From<PublicProperty<string>>()
85+
.To<PublicField<PublicField<PublicField<string>>>>()
86+
.Map("Deep!")
87+
.To(x => x.Value.Value.Value);
88+
89+
var source = new PublicProperty<string>();
90+
var target = new PublicField<PublicField<PublicField<string>>>();
91+
var result = mapper.Map(source).Over(target);
92+
93+
result.Value.ShouldNotBeNull();
94+
result.Value.Value.ShouldNotBeNull();
95+
result.Value.Value.Value.ShouldNotBeNull("Deep!");
96+
}
97+
}
98+
7899
[Fact]
79100
public void ShouldApplyAConfiguredMember()
80101
{

AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreationCallbacks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void ShouldCallAnObjectCreatedCallbackWithASourceObject()
171171
var nonMatchingTarget = new Person { Name = "Fred" };
172172
var nonMatchingResult = mapper.Map(nonMatchingSource).OnTo(nonMatchingTarget);
173173

174-
nonMatchingResult.Address.Line2.ShouldBeNull();
174+
nonMatchingResult.Address.ShouldBeNull();
175175
nonMatchingResult.Name.ShouldBe("Fred");
176176

177177
var matchingSource = new PersonViewModel { Name = "Betty" };

AgileMapper.UnitTests/Configuration/WhenIgnoringMembers.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,26 @@ public void ShouldIgnoreMultipleConfiguredMembers()
9898
{
9999
using (var mapper = Mapper.CreateNew())
100100
{
101+
var source = new
102+
{
103+
Id = Guid.NewGuid().ToString(),
104+
Name = "Bilbo",
105+
AddressLine1 = "House Street",
106+
AddressLine2 = "Town City"
107+
};
108+
101109
mapper.WhenMapping
102-
.From<PersonViewModel>()
110+
.From(source)
103111
.ToANew<Person>()
104112
.Ignore(p => p.Name, p => p.Address.Line1);
105113

106-
var source = new PersonViewModel
107-
{
108-
Id = Guid.NewGuid(),
109-
Name = "Bilbo",
110-
AddressLine1 = "House Street"
111-
};
112114
var matchingResult = mapper.Map(source).ToANew<Person>();
113115

114-
matchingResult.Id.ShouldBe(source.Id);
116+
matchingResult.Id.ToString().ShouldBe(source.Id);
115117
matchingResult.Name.ShouldBeNull();
116118
matchingResult.Address.ShouldNotBeNull();
117119
matchingResult.Address.Line1.ShouldBeNull();
120+
matchingResult.Address.Line2.ShouldBe("Town City");
118121
}
119122
}
120123

AgileMapper.UnitTests/WhenMappingOverComplexTypeMembers.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,34 @@ public void ShouldHandleNoMatchingSourceMember()
5454
result.Address.ShouldNotBeNull();
5555
}
5656

57+
// See https://github.com/agileobjects/AgileMapper/issues/21
58+
[Fact]
59+
public void ShouldNotPopulateAMemberWithNoMatchingSource()
60+
{
61+
var source = new { Name = "Customer!" };
62+
var target = new Customer { Name = "No-one", Address = default(Address) };
63+
var result = Mapper.Map(source).Over(target);
64+
65+
result.Name.ShouldBe("Customer!");
66+
result.Address.ShouldBeNull();
67+
}
68+
69+
[Fact]
70+
public void ShouldNotOverwriteAMemberWithNoMatchingSource()
71+
{
72+
var source = new { Name = "Scooby" };
73+
var target = new MysteryCustomer
74+
{
75+
Name = "No-one",
76+
Address = new Address { Line1 = "Leave me alone!" }
77+
};
78+
var originalAddress = target.Address;
79+
var result = Mapper.Map(source).Over(target);
80+
81+
result.Name.ShouldBe("Scooby");
82+
result.Address.ShouldBeSameAs(originalAddress);
83+
}
84+
5785
[Fact]
5886
public void ShouldApplyAConfiguredConstant()
5987
{

AgileMapper.UnitTests/WhenMappingToNewComplexTypeMembers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void ShouldHandleNoMatchingSourceMember()
4242

4343
var result = Mapper.Map(source).ToANew<Customer>();
4444

45-
result.Address.ShouldNotBeNull();
45+
result.Address.ShouldBeNull();
4646
}
4747

4848
[Fact]

AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,13 @@ protected override IEnumerable<Expression> GetObjectPopulation(IObjectMappingDat
144144
}
145145

146146
var assignCreatedObject = postCreationCallback != null;
147+
var hasMemberPopulations = MemberPopulationsExist(populationsAndCallbacks);
147148

148149
var instanceVariableValue = TargetObjectResolutionFactory.GetObjectResolution(
149150
md => _constructionFactory.GetNewObjectCreation(md),
150151
mappingData,
151-
assignCreatedObject);
152+
assignCreatedObject,
153+
hasMemberPopulations: hasMemberPopulations);
152154

153155
var instanceVariableAssignment = mapperData.InstanceVariable.AssignTo(instanceVariableValue);
154156
yield return instanceVariableAssignment;
@@ -258,6 +260,9 @@ private static void CreateSourceMemberTypeTesterIfRequired(
258260
mappingData.MapperKey.AddSourceMemberTypeTester(typeTestLambda.Compile());
259261
}
260262

263+
private static bool MemberPopulationsExist(IEnumerable<Expression> populationsAndCallbacks)
264+
=> populationsAndCallbacks.Any(population => population.NodeType != ExpressionType.Constant);
265+
261266
protected override Expression GetReturnValue(ObjectMapperData mapperData) => mapperData.InstanceVariable;
262267

263268
public override void Reset() => _constructionFactory.Reset();

AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public static Expression GetObjectResolution(
1111
Func<IObjectMappingData, Expression> constructionFactory,
1212
IObjectMappingData mappingData,
1313
bool assignCreatedObject = false,
14-
bool assignTargetObject = false)
14+
bool assignTargetObject = false,
15+
bool hasMemberPopulations = true)
1516
{
1617
var mapperData = mappingData.MapperData;
1718

@@ -30,6 +31,15 @@ public static Expression GetObjectResolution(
3031
return mapperData.TargetObject;
3132
}
3233

34+
if (UseNullFallbackValue(mapperData, objectValue, hasMemberPopulations))
35+
{
36+
objectValue = mapperData.TargetMember.Type.ToDefaultExpression();
37+
38+
assignCreatedObject =
39+
assignTargetObject =
40+
mapperData.Context.UsesMappingDataObjectAsParameter = false;
41+
}
42+
3343
if (assignCreatedObject)
3444
{
3545
mapperData.Context.UsesMappingDataObjectAsParameter = true;
@@ -46,6 +56,23 @@ public static Expression GetObjectResolution(
4656
return objectValue;
4757
}
4858

59+
private static bool UseNullFallbackValue(
60+
IMemberMapperData mapperData,
61+
Expression objectConstruction,
62+
bool hasMemberPopulations)
63+
{
64+
if (hasMemberPopulations ||
65+
(objectConstruction.NodeType != ExpressionType.New) ||
66+
mapperData.SourceMember.Matches(mapperData.TargetMember))
67+
{
68+
return false;
69+
}
70+
71+
var objectNewing = (NewExpression)objectConstruction;
72+
73+
return objectNewing.Arguments.None();
74+
}
75+
4976
private static Expression AddExistingTargetCheckIfAppropriate(Expression value, IObjectMappingData mappingData)
5077
{
5178
if (mappingData.MapperData.TargetCouldBePopulated())

AgileMapper/ObjectPopulation/MappingFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public static Expression GetDirectAccessMapping(
196196
var useLocalSourceValueVariable =
197197
ShouldUseLocalSourceValueVariable(mappingValues.SourceValue, mapping, mapperData);
198198

199-
Expression sourceValue, sourceValueVariableValue = null;
199+
Expression sourceValue, sourceValueVariableValue;
200200

201201
if (useLocalSourceValueVariable)
202202
{
@@ -207,6 +207,7 @@ public static Expression GetDirectAccessMapping(
207207
else
208208
{
209209
sourceValue = mappingValues.SourceValue;
210+
sourceValueVariableValue = null;
210211
}
211212

212213
var replacementsByTarget = new ExpressionReplacementDictionary

AgileMapper/ObjectPopulation/ObjectMappingData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private ObjectMappingData(
7575

7676
public IObjectMapper Mapper
7777
{
78-
get { return _mapper ?? (_mapper = MapperContext.ObjectMapperFactory.Create(this)); }
78+
get => _mapper ?? (_mapper = MapperContext.ObjectMapperFactory.Create(this));
7979
set
8080
{
8181
_mapper = (ObjectMapper<TSource, TTarget>)value;

0 commit comments

Comments
 (0)