diff --git a/src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs b/src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs index d2239a18..1e1e27d9 100644 --- a/src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs +++ b/src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs @@ -12,12 +12,16 @@ public class WhenPropertyNullablePropagationRegression /// /// [TestMethod] - public async Task NotNullableStructMapToNotNullableCorrect() + public void NotNullableStructMapToNotNullableCorrect() { - TypeAdapterConfig + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.InnerAmount, src => src.Inner.Amount); + + TypeAdapterConfig .NewConfig() - .Map(dest => dest.Amount, src => src.Amount) - .Map(dest => dest.InnerAmount, src => src.Inner.Amount); + .IgnoreNullValues(true); Foo858 foo = new() @@ -32,12 +36,13 @@ public async Task NotNullableStructMapToNotNullableCorrect() // Act var bar = foo.Adapt(); + // Assert bar.InnerAmount.Amount.ShouldBe(10m); } [TestMethod] - public async Task NotNullableStructMapToNullableCorrect() + public void NotNullableStructMapToNullableCorrect() { TypeAdapterConfig .NewConfig() @@ -61,6 +66,74 @@ public async Task NotNullableStructMapToNullableCorrect() bar.InnerAmount?.Amount.ShouldBe(10m); } + [TestMethod] + public void IgnoreNullValueWorkCorrect() + { + TypeAdapterConfig + .NewConfig() + .IgnoreNullValues(true) + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.InnerAmount, src => src.Inner.Amount); + + Foo858 foo = new() + { + Amount = new(1, Currency858.Usd), + Inner = new() + { + Amount = new(10, Currency858.Eur), + Int = 100, + } + }; + + var nullFoo = new Foo858() { Amount = new(2, Currency858.Ron), Inner = null }; + + // Act + var bar = foo.Adapt(); + nullFoo.Adapt(bar); + + // Assert + bar.InnerAmount.Amount.ShouldBe(10m); + bar.Amount.Amount.ShouldBe(2m); + bar.Amount.Currency.ShouldBe(Currency858.Ron); + } + + [TestMethod] + public void MapToTargetWorkCorrect() + { + TypeAdapterConfig + .NewConfig() + .IgnoreNullValues(true) + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.InnerAmount, src => src.Inner.Amount); + + Foo858 foo = new() + { + Amount = new(1, Currency858.Usd), + Inner = new() + { + Amount = new(10, Currency858.Eur), + Int = 100, + } + }; + + var nullFoo = new Foo858() { Amount = new(2, Currency858.Ron), Inner = new() + { + Amount = new(20, Currency858.Eur), + Int = 100, + } + }; + + // Act + var bar = foo.Adapt(); + nullFoo.Adapt(bar); + + // Assert + bar.InnerAmount.Amount.ShouldBe(20m); + bar.Amount.Amount.ShouldBe(2m); + bar.Amount.Currency.ShouldBe(Currency858.Ron); + } + + } #region TestClasses diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 22314a5b..a8b7a4c0 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -132,7 +132,7 @@ select fn(src, destinationMember, arg)) { propertyModel.Getter = arg.MapType == MapType.Projection ? getter - : getter.ApplyPropertyNullPropagation(propertyModel); + : getter.ApplyPropertyNullPropagation(); properties.Add(propertyModel); } else diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 78cfacc1..a17b079a 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -114,7 +114,17 @@ protected override Expression CreateBlockExpression(Expression source, Expressio ? member.DestinationMember.GetExpression(destination) : null; - var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember); + Expression adapt; + + // convert ApplyNullable Propagation for NotPrimitive Nullable types + if (member.Getter is ConditionalExpression cond && member.Getter.Type.IsNotPrimitiveNullableType() + && !member.DestinationMember.Type.IsNullable()) + { + var value = CreateAdaptExpression(cond.IfTrue.GetNotPrimitiveNullableValue(), member.DestinationMember.Type, arg, member, destMember); + adapt = Expression.Condition(cond.Test, value, member.DestinationMember.Type.CreateDefault()); + } + else + adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember); if (member.UseDestinationValue && member.DestinationMember.Type.IsMapsterImmutable() @@ -251,7 +261,17 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre if (member.DestinationMember.SetterModifier == AccessModifier.None) continue; - var value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + Expression value; + + // convert ApplyNullable Propagation for NotPrimitive Nullable types + if (member.Getter is ConditionalExpression cond && member.Getter.Type.IsNotPrimitiveNullableType() + && !member.DestinationMember.Type.IsNullable()) + { + var adapt = CreateAdaptExpression(cond.IfTrue.GetNotPrimitiveNullableValue(), member.DestinationMember.Type, arg, member); + value = Expression.Condition(cond.Test, adapt, member.DestinationMember.Type.CreateDefault()); + } + else + value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); //special null property check for projection //if we don't set null to property, EF will create empty object diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index c00c5391..4e6af8a7 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -407,7 +407,7 @@ public static Expression NullableEnumExtractor(this Expression param) return param; } - public static Expression ApplyPropertyNullPropagation(this Expression getter, MemberMapping property) + public static Expression ApplyPropertyNullPropagation(this Expression getter) { var current = getter; var result = getter; @@ -421,7 +421,7 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter, Me break; if (expr.NodeType == ExpressionType.Parameter && condition != null) { - if (property.DestinationMember.Type.CanBeNull() && !getter.CanBeNull()) + if (!getter.CanBeNull()) { var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type)); return Expression.Condition(condition, transform, transform.Type.CreateDefault()); @@ -532,5 +532,23 @@ internal static Expression GetNameConverterExpression(Func conve return Expression.Constant(converter); } + public static bool IsNotPrimitiveNullableType(this Type type) + { + return Nullable.GetUnderlyingType(type) != null && !type.IsMapsterPrimitive(); + } + + public static Expression GetNotPrimitiveNullableValue(this Expression exp) + { + if (exp.Type.IsNotPrimitiveNullableType()) + { + var getValueOrDefaultMethod = exp.Type.GetMethod("GetValueOrDefault", Type.EmptyTypes); + var getValue = Expression.Call(exp, getValueOrDefaultMethod); + + return getValue; + } + + return exp; + } + } }