Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/Mapster.Tests/WhenCtorNullableParamMapping.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System.Collections.Generic;

namespace Mapster.Tests
{
Expand Down Expand Up @@ -60,6 +61,27 @@ public void Dto_To_Domain_AbstractClassNull_MapsCorrectly()
}


/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/943
/// </summary>
[TestMethod]
public void NullableCtorPropagationCurrentWorkWithDestinationTransform()
{
var config = new TypeAdapterConfig();

config.Default
.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull);

// Arrange
var fooDto = new FooDto943();

// Act
var foo = fooDto.Adapt<Foo943>(config);

// Assert
foo.Strings.ShouldNotBeNull();
}

#region Immutable classes with private setters, map via ctors
private abstract class AbstractDomainTestClass
{
Expand Down Expand Up @@ -96,6 +118,13 @@ public DomainTestClass(
#endregion

#region DTO classes

class FooDto943
{
public string[] Strings { get; set; }
}

record Foo943(List<string> Strings);
private abstract class AbstractDtoTestClass
{
public string AbstractProperty { get; set; }
Expand Down
24 changes: 19 additions & 5 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

#region Build the Adapter Model

protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null, bool ctorMapping = false, ClassModel recordRestorMemberModel = null)

Check warning on line 18 in src/Mapster/Adapters/BaseClassAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var destinationMembers = classModel.Members;
var unmappedDestinationMembers = new List<string>();
Expand Down Expand Up @@ -213,7 +213,7 @@
&& ignore.Condition == null;
}

protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg, Expression? destination, ClassModel recordRestorParamModel = null)

Check warning on line 216 in src/Mapster/Adapters/BaseClassAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var members = classConverter.Members;

Expand Down Expand Up @@ -247,7 +247,7 @@
}
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)
Expand Down
24 changes: 2 additions & 22 deletions src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/Mapster/Utils/ExpressionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading