Skip to content
Merged
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
83 changes: 78 additions & 5 deletions src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ public class WhenPropertyNullablePropagationRegression
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task NotNullableStructMapToNotNullableCorrect()
public void NotNullableStructMapToNotNullableCorrect()
{
TypeAdapterConfig<Foo858, Bar858>
TypeAdapterConfig<Foo858, Bar858>
.NewConfig()
.Map(dest => dest.Amount, src => src.Amount)
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);

TypeAdapterConfig<Bar858, Bar858>
.NewConfig()
.Map(dest => dest.Amount, src => src.Amount)
.Map(dest => dest.InnerAmount, src => src.Inner.Amount);
.IgnoreNullValues(true);


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

// Act
var bar = foo.Adapt<Bar858>();

// Assert
bar.InnerAmount.Amount.ShouldBe(10m);
}

[TestMethod]
public async Task NotNullableStructMapToNullableCorrect()
public void NotNullableStructMapToNullableCorrect()
{
TypeAdapterConfig<Foo858, Bar858Nullable>
.NewConfig()
Expand All @@ -61,6 +66,74 @@ public async Task NotNullableStructMapToNullableCorrect()
bar.InnerAmount?.Amount.ShouldBe(10m);
}

[TestMethod]
public void IgnoreNullValueWorkCorrect()
{
TypeAdapterConfig<Foo858, Bar858>
.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<Bar858>();
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<Foo858, Bar858>
.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<Bar858>();
nullFoo.Adapt(bar);

// Assert
bar.InnerAmount.Amount.ShouldBe(20m);
bar.Amount.Amount.ShouldBe(2m);
bar.Amount.Currency.ShouldBe(Currency858.Ron);
}


}

#region TestClasses
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 @@ -132,7 +132,7 @@
{
propertyModel.Getter = arg.MapType == MapType.Projection
? getter
: getter.ApplyPropertyNullPropagation(propertyModel);
: getter.ApplyPropertyNullPropagation();
properties.Add(propertyModel);
}
else
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
24 changes: 22 additions & 2 deletions src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
22 changes: 20 additions & 2 deletions src/Mapster/Utils/ExpressionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Expand Down Expand Up @@ -532,5 +532,23 @@ internal static Expression GetNameConverterExpression(Func<string, string> 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;
}

}
}
Loading