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
51 changes: 51 additions & 0 deletions src/Mapster.Tests/WhenAddCtorNullablePropagation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Mapster.Tests.Classes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

namespace Mapster.Tests
{
[TestClass]
public class WhenAddCtorNullablePropagation
{

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/898
/// </summary>
[TestMethod]
public void NullablePropagationFromCtorWorking()
{
var source = new List<OrderEntity898>();

source.Add(new OrderEntity898() { Id = 1, Cod = new OrderCodEntity898 { Value = 42L } });
source.Add(new OrderEntity898() { Id = 2, Cod = null });

var str = new OrderEntity898() { Id = 1, Cod = new OrderCodEntity898 { Value = 42L } }.BuildAdapter().CreateProjectionExpression<OrderDto898>();

var result = source.AsQueryable().ProjectToType<OrderDto898>().ToList();
}

}

#region TestClasses

public record OrderDto898(int Id, OrderCodDto898? Cod);
public record OrderCodDto898(long Value);


public class OrderEntity898
{
public int Id { get; set; }
public int? CodId { get; set; }
public OrderCodEntity898? Cod { get; set; }
}

public class OrderCodEntity898
{
public int Id { get; set; }
public long Value { get; set; }
}


#endregion TestClasses
}
5 changes: 4 additions & 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,13 +213,14 @@
&& 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;

var arguments = new List<Expression>();
foreach (var member in members)
{
arg.Context.NullChecks.UnionWith(members.Select(x=>(x.Getter,arg)));
var parameterInfo = (ParameterInfo)member.DestinationMember.Info!;
var defaultConst = parameterInfo.IsOptional
? Expression.Constant(parameterInfo.DefaultValue, member.DestinationMember.Type)
Expand All @@ -245,7 +246,9 @@
defaultConst);
}
else
getter = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);
getter = member.Getter
.ApplyNullPropagationFromCtor(CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member), arg);


if (member.Ignore.Condition != null)
{
Expand Down
1 change: 1 addition & 0 deletions src/Mapster/Compile/CompileContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class CompileContext
public int? MaxDepth { get; set; }
public int Depth { get; set; }
public HashSet<ParameterExpression> ExtraParameters { get; } = new();
public HashSet<(Expression param, CompileArgument arg)> NullChecks { get; } = new();

internal bool IsSubFunction()
{
Expand Down
41 changes: 40 additions & 1 deletion src/Mapster/Utils/ExpressionEx.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Linq.Expressions;
using Mapster.Models;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Mapster.Utils
Expand Down Expand Up @@ -433,6 +434,44 @@ public static Expression ApplyNullPropagation(this Expression getter)
return getter;
}

public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg)
{
Expression? condition = null;
var current = getter;
var checks = arg.Context.NullChecks
.Where(x=> !object.ReferenceEquals(x.arg,arg))
.Select(x=>x.param.Type);

while (current != null)
{
Expression? compareNull = null;

if (current.CanBeNull() && current is not ParameterExpression)
compareNull = Expression.NotEqual(current, Expression.Constant(null, current.Type));
else if (current.CanBeNull() && current is ParameterExpression
&& !checks.Contains(current.Type))
compareNull = Expression.NotEqual(current, Expression.Constant(null, current.Type));

if (compareNull != null)
{
if (condition == null)
condition = compareNull;
else
condition = Expression.AndAlso(compareNull, condition);
}

if (current is MemberExpression member)
current = member.Expression;
else
current = null;
}

if (condition == null)
return adapt;

return Expression.Condition(condition, adapt, adapt.Type.CreateDefault());
}

public static string? GetMemberPath(this LambdaExpression lambda, bool firstLevelOnly = false, bool noError = false)
{
var props = new List<string>();
Expand Down
Loading