Skip to content

Commit 8b02e28

Browse files
authored
fix: re-fix #858 and fix IgnoreNullValues regression from MapToTarget cases in #905 (#908)
1 parent 6d82c35 commit 8b02e28

File tree

4 files changed

+121
-10
lines changed

4 files changed

+121
-10
lines changed

src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ public class WhenPropertyNullablePropagationRegression
1212
/// </summary>
1313
/// <returns></returns>
1414
[TestMethod]
15-
public async Task NotNullableStructMapToNotNullableCorrect()
15+
public void NotNullableStructMapToNotNullableCorrect()
1616
{
17-
TypeAdapterConfig<Foo858, Bar858>
17+
TypeAdapterConfig<Foo858, Bar858>
18+
.NewConfig()
19+
.Map(dest => dest.Amount, src => src.Amount)
20+
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);
21+
22+
TypeAdapterConfig<Bar858, Bar858>
1823
.NewConfig()
19-
.Map(dest => dest.Amount, src => src.Amount)
20-
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);
24+
.IgnoreNullValues(true);
2125

2226

2327
Foo858 foo = new()
@@ -32,12 +36,13 @@ public async Task NotNullableStructMapToNotNullableCorrect()
3236

3337
// Act
3438
var bar = foo.Adapt<Bar858>();
39+
3540
// Assert
3641
bar.InnerAmount.Amount.ShouldBe(10m);
3742
}
3843

3944
[TestMethod]
40-
public async Task NotNullableStructMapToNullableCorrect()
45+
public void NotNullableStructMapToNullableCorrect()
4146
{
4247
TypeAdapterConfig<Foo858, Bar858Nullable>
4348
.NewConfig()
@@ -61,6 +66,74 @@ public async Task NotNullableStructMapToNullableCorrect()
6166
bar.InnerAmount?.Amount.ShouldBe(10m);
6267
}
6368

69+
[TestMethod]
70+
public void IgnoreNullValueWorkCorrect()
71+
{
72+
TypeAdapterConfig<Foo858, Bar858>
73+
.NewConfig()
74+
.IgnoreNullValues(true)
75+
.Map(dest => dest.Amount, src => src.Amount)
76+
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);
77+
78+
Foo858 foo = new()
79+
{
80+
Amount = new(1, Currency858.Usd),
81+
Inner = new()
82+
{
83+
Amount = new(10, Currency858.Eur),
84+
Int = 100,
85+
}
86+
};
87+
88+
var nullFoo = new Foo858() { Amount = new(2, Currency858.Ron), Inner = null };
89+
90+
// Act
91+
var bar = foo.Adapt<Bar858>();
92+
nullFoo.Adapt(bar);
93+
94+
// Assert
95+
bar.InnerAmount.Amount.ShouldBe(10m);
96+
bar.Amount.Amount.ShouldBe(2m);
97+
bar.Amount.Currency.ShouldBe(Currency858.Ron);
98+
}
99+
100+
[TestMethod]
101+
public void MapToTargetWorkCorrect()
102+
{
103+
TypeAdapterConfig<Foo858, Bar858>
104+
.NewConfig()
105+
.IgnoreNullValues(true)
106+
.Map(dest => dest.Amount, src => src.Amount)
107+
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);
108+
109+
Foo858 foo = new()
110+
{
111+
Amount = new(1, Currency858.Usd),
112+
Inner = new()
113+
{
114+
Amount = new(10, Currency858.Eur),
115+
Int = 100,
116+
}
117+
};
118+
119+
var nullFoo = new Foo858() { Amount = new(2, Currency858.Ron), Inner = new()
120+
{
121+
Amount = new(20, Currency858.Eur),
122+
Int = 100,
123+
}
124+
};
125+
126+
// Act
127+
var bar = foo.Adapt<Bar858>();
128+
nullFoo.Adapt(bar);
129+
130+
// Assert
131+
bar.InnerAmount.Amount.ShouldBe(20m);
132+
bar.Amount.Amount.ShouldBe(2m);
133+
bar.Amount.Currency.ShouldBe(Currency858.Ron);
134+
}
135+
136+
64137
}
65138

66139
#region TestClasses

src/Mapster/Adapters/BaseClassAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ select fn(src, destinationMember, arg))
132132
{
133133
propertyModel.Getter = arg.MapType == MapType.Projection
134134
? getter
135-
: getter.ApplyPropertyNullPropagation(propertyModel);
135+
: getter.ApplyPropertyNullPropagation();
136136
properties.Add(propertyModel);
137137
}
138138
else

src/Mapster/Adapters/ClassAdapter.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,17 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
114114
? member.DestinationMember.GetExpression(destination)
115115
: null;
116116

117-
var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember);
117+
Expression adapt;
118+
119+
// convert ApplyNullable Propagation for NotPrimitive Nullable types
120+
if (member.Getter is ConditionalExpression cond && member.Getter.Type.IsNotPrimitiveNullableType()
121+
&& !member.DestinationMember.Type.IsNullable())
122+
{
123+
var value = CreateAdaptExpression(cond.IfTrue.GetNotPrimitiveNullableValue(), member.DestinationMember.Type, arg, member, destMember);
124+
adapt = Expression.Condition(cond.Test, value, member.DestinationMember.Type.CreateDefault());
125+
}
126+
else
127+
adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember);
118128

119129
if (member.UseDestinationValue
120130
&& member.DestinationMember.Type.IsMapsterImmutable()
@@ -251,7 +261,17 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre
251261
if (member.DestinationMember.SetterModifier == AccessModifier.None)
252262
continue;
253263

254-
var value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);
264+
Expression value;
265+
266+
// convert ApplyNullable Propagation for NotPrimitive Nullable types
267+
if (member.Getter is ConditionalExpression cond && member.Getter.Type.IsNotPrimitiveNullableType()
268+
&& !member.DestinationMember.Type.IsNullable())
269+
{
270+
var adapt = CreateAdaptExpression(cond.IfTrue.GetNotPrimitiveNullableValue(), member.DestinationMember.Type, arg, member);
271+
value = Expression.Condition(cond.Test, adapt, member.DestinationMember.Type.CreateDefault());
272+
}
273+
else
274+
value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);
255275

256276
//special null property check for projection
257277
//if we don't set null to property, EF will create empty object

src/Mapster/Utils/ExpressionEx.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ public static Expression NullableEnumExtractor(this Expression param)
407407

408408
return param;
409409
}
410-
public static Expression ApplyPropertyNullPropagation(this Expression getter, MemberMapping property)
410+
public static Expression ApplyPropertyNullPropagation(this Expression getter)
411411
{
412412
var current = getter;
413413
var result = getter;
@@ -421,7 +421,7 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter, Me
421421
break;
422422
if (expr.NodeType == ExpressionType.Parameter && condition != null)
423423
{
424-
if (property.DestinationMember.Type.CanBeNull() && !getter.CanBeNull())
424+
if (!getter.CanBeNull())
425425
{
426426
var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type));
427427
return Expression.Condition(condition, transform, transform.Type.CreateDefault());
@@ -532,5 +532,23 @@ internal static Expression GetNameConverterExpression(Func<string, string> conve
532532
return Expression.Constant(converter);
533533
}
534534

535+
public static bool IsNotPrimitiveNullableType(this Type type)
536+
{
537+
return Nullable.GetUnderlyingType(type) != null && !type.IsMapsterPrimitive();
538+
}
539+
540+
public static Expression GetNotPrimitiveNullableValue(this Expression exp)
541+
{
542+
if (exp.Type.IsNotPrimitiveNullableType())
543+
{
544+
var getValueOrDefaultMethod = exp.Type.GetMethod("GetValueOrDefault", Type.EmptyTypes);
545+
var getValue = Expression.Call(exp, getValueOrDefaultMethod);
546+
547+
return getValue;
548+
}
549+
550+
return exp;
551+
}
552+
535553
}
536554
}

0 commit comments

Comments
 (0)