diff --git a/src/Mapster.Tests/WhenCtorNullableParamMapping.cs b/src/Mapster.Tests/WhenCtorNullableParamMapping.cs index bef0b16f..e1884624 100644 --- a/src/Mapster.Tests/WhenCtorNullableParamMapping.cs +++ b/src/Mapster.Tests/WhenCtorNullableParamMapping.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; +using System.Collections.Generic; namespace Mapster.Tests { @@ -60,6 +61,27 @@ public void Dto_To_Domain_AbstractClassNull_MapsCorrectly() } + /// + /// https://github.com/MapsterMapper/Mapster/issues/943 + /// + [TestMethod] + public void NullableCtorPropagationCurrentWorkWithDestinationTransform() + { + var config = new TypeAdapterConfig(); + + config.Default + .AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + + // Arrange + var fooDto = new FooDto943(); + + // Act + var foo = fooDto.Adapt(config); + + // Assert + foo.Strings.ShouldNotBeNull(); + } + #region Immutable classes with private setters, map via ctors private abstract class AbstractDomainTestClass { @@ -96,6 +118,13 @@ public DomainTestClass( #endregion #region DTO classes + + class FooDto943 + { + public string[] Strings { get; set; } + } + + record Foo943(List Strings); private abstract class AbstractDtoTestClass { public string AbstractProperty { get; set; } diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 31d7541c..b31a0dbe 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -448,7 +448,7 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex } } - private static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null) + internal static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null) { var mapType = arg.MapType == MapType.MapToTarget && destination == null ? MapType.Map : mapping?.UseDestinationValue == true ? MapType.MapToTarget : @@ -501,10 +501,24 @@ internal Expression CreateAdaptExpression(Expression source, Type destinationTyp //adapt(_source); var notUsingDestinationValue = mapping is not { UseDestinationValue: true }; - var exp = _source.Type == destinationType && arg.Settings.ShallowCopyForSameType == true && notUsingDestinationValue - && rule == null - ? _source - : CreateAdaptExpressionCore(_source, destinationType, arg, mapping, destination); + Expression exp; + + if (_source.Type == destinationType && arg.Settings.ShallowCopyForSameType == true + && notUsingDestinationValue && rule == null) + exp = _source; + else if (source is ConditionalExpression cond && mapping != null) + { + // convert ApplyNullable Propagation for NotPrimitive Nullable types + if (mapping.Getter.Type.IsNotPrimitiveNullableType() && !mapping.DestinationMember.Type.IsNullable()) + { + var adapt = CreateAdaptExpressionCore(cond.IfTrue.GetNotPrimitiveNullableValue(), mapping.DestinationMember.Type, arg, mapping); + exp = Expression.Condition(cond.Test, adapt, mapping.DestinationMember.Type.CreateDefault()); + } + else + exp = CreateAdaptExpressionCore(_source, destinationType, arg, mapping, destination); + } + else + exp = CreateAdaptExpressionCore(_source, destinationType, arg, mapping, destination); //transform(adapt(_source)); if (notUsingDestinationValue) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 1d6f6b64..d719409e 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -247,7 +247,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi } else getter = member.Getter - .ApplyNullPropagationFromCtor(CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member), arg); + .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member), arg); if (member.Ignore.Condition != null) diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 042c0270..27d09d9b 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -114,17 +114,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio ? member.DestinationMember.GetExpression(destination) : null; - 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); + var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember); if (member.UseDestinationValue && member.DestinationMember.Type.IsMapsterImmutable() @@ -262,17 +252,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre if (member.DestinationMember.SetterModifier == AccessModifier.None) continue; - 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); + var 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 4e6af8a7..b7ffc365 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -483,7 +483,12 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex if (condition == null) return adapt; - return Expression.Condition(condition, adapt, adapt.Type.CreateDefault()); + // add supporting DestinationTransforms + var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(adapt.Type)); + if (transform != null) + return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, Expression.Default(adapt.Type))); + + return Expression.Condition(condition, adapt, Expression.Default(adapt.Type)); } public static string? GetMemberPath(this LambdaExpression lambda, bool firstLevelOnly = false, bool noError = false)