Skip to content
Draft
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
109 changes: 109 additions & 0 deletions src/Mapster.Tests/WhenNullPropagationRegression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Mapster.Tests.Classes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System;

namespace Mapster.Tests;

[TestClass]
public class WhenNullPropagationRegression
{
[TestMethod]
public void WhenCustomDefaultWorked()
{
string customdefault = "42";

TypeAdapterConfig<SimplePocoToNullPropagation, SimpleDtoToNullPropagation>.NewConfig()
.Map(dest => dest.AnotherName, src => src.Name).SourceDefaultValue(customdefault)
.Map(dest => dest.LastModified, src => DateTime.Now)
.Compile();

var poco = new SimplePocoToNullPropagation { Id = Guid.NewGuid(), Name = null};

var dto = poco.Adapt<SimpleDtoToNullPropagation>();

dto.Id.ShouldBe(poco.Id);
dto.Name.ShouldBe(null);
dto.AnotherName.ShouldBe(customdefault);
}

[TestMethod]
public void WhenCustomDefaultMapPathWorked()
{
string customdefault = "Default Location";

TypeAdapterConfig<PocoToPathNullPropagation, DtoToPathNullPropagation>.NewConfig()
.Map(dest => dest.Location, src => src.Child.Address.Location).SourceDefaultValue(customdefault);

var poco = new PocoToPathNullPropagation
{
Id = Guid.NewGuid(),
Child = new()
};

var dto = poco.Adapt<DtoToPathNullPropagation>();
dto.Id.ShouldBe(poco.Id);
dto.Location.ShouldBe(customdefault);
}

[TestMethod]
public void WhenThrowWhenNullWorked()
{
TypeAdapterConfig<PocoToPathNullPropagation, DtoToPathNullPropagation>.NewConfig()
.Map(dest => dest.Location, src => src.Child.Address.Location).ThrowWhenNull();

var poco = new PocoToPathNullPropagation
{
Id = Guid.NewGuid(),
Child = new()
};

Should.Throw<NullReferenceException>(() =>
{
var dto = poco.Adapt<DtoToPathNullPropagation>();
});
}

#region Classes

public class SimplePocoToNullPropagation
{
public Guid Id { get; set; }
public string? Name { get; set; }
}

public class SimpleDtoToNullPropagation
{
public Guid Id { get; set; }
public string Name { get; set; }

public string AnotherName { get; set; }
public DateTime LastModified { get; set; }

}

public class PocoToPathNullPropagation
{
public Guid Id { get; set; }
public ChildPocoToNullPropagation? Child { get; set; }
}

public class DtoToPathNullPropagation
{
public Guid Id { get; set; }
public Address Address { get; set; }
public string Location { get; set; }
}

public class ChildPocoToNullPropagation
{
public AddressNullPropagation? Address { get; set; }
}
public class AddressNullPropagation
{
public string? Number { get; set; }
public string? Location { get; set; }
}

#endregion Classes
}
2 changes: 2 additions & 0 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ protected virtual void DecorateRule(TypeAdapterRule rule) { }

protected virtual bool CanInline(Expression source, Expression? destination, CompileArgument arg)
{
if (arg.Settings.Resolvers.Any(x=>x.IsThrowWhenNull))
return false;
if (arg.MapType == MapType.Projection)
return true;

Expand Down
27 changes: 20 additions & 7 deletions src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,26 @@ select fn(src, destinationMember, arg))
Destination = (ParameterExpression?)destination,
UseDestinationValue = arg.MapType != MapType.Projection && destinationMember.UseDestinationValue(arg),
};
if (getter != null)
{
propertyModel.Getter = arg.MapType == MapType.Projection
? getter
: getter.ApplyNullPropagation();
properties.Add(propertyModel);
}
if (getter != null)
{
var customDefault = arg.Settings.Resolvers
.Where(x=>x.DefaultValue != null && x.DestinationMemberName == destinationMember.Name)
.FirstOrDefault()?.DefaultValue;

var whenNullisThrow = arg.Settings.Resolvers
.Where(x => x.IsThrowWhenNull && x.DestinationMemberName == destinationMember.Name)
.FirstOrDefault()?.IsThrowWhenNull;

if (whenNullisThrow == true)
propertyModel.Getter = getter;
else
{
propertyModel.Getter = arg.MapType == MapType.Projection
? getter
: getter.ApplyNullPropagation(customDefault);
}
properties.Add(propertyModel);
}
else
{
if (arg.Settings.IgnoreNonMapped != true &&
Expand Down
13 changes: 12 additions & 1 deletion src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,18 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
tuple.Item1.Add(adapt);
}
else
lines.Add(adapt);
{
var whenNullisThrow = arg.Settings.Resolvers
.Where(x => x.IsThrowWhenNull && x.DestinationMemberName == member.DestinationMember.Name)
.FirstOrDefault()?.IsThrowWhenNull;

if (whenNullisThrow == true)
{
lines.Add(adapt.ApplyThrowPropagation());
}
else
lines.Add(adapt);
}
}

if (conditions != null)
Expand Down
2 changes: 2 additions & 0 deletions src/Mapster/Models/InvokerModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class InvokerModel
public LambdaExpression? Invoker { get; set; }
public string? SourceMemberName { get; set; }
public LambdaExpression? Condition { get; set; }
public LambdaExpression? DefaultValue { get; set; }
public bool IsThrowWhenNull { get; set; }
public bool IsChildPath { get; set; }

public InvokerModel? Next(ParameterExpression source, string destMemberName)
Expand Down
Loading
Loading