diff --git a/src/Mapster.Tests/WhenAddCtorNullablePropagation.cs b/src/Mapster.Tests/WhenAddCtorNullablePropagation.cs
new file mode 100644
index 00000000..b13eefa6
--- /dev/null
+++ b/src/Mapster.Tests/WhenAddCtorNullablePropagation.cs
@@ -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
+ {
+
+ ///
+ /// https://github.com/MapsterMapper/Mapster/issues/898
+ ///
+ [TestMethod]
+ public void NullablePropagationFromCtorWorking()
+ {
+ var source = new List();
+
+ 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();
+
+ var result = source.AsQueryable().ProjectToType().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
+}
diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs
index f092c3ab..e36ad7a8 100644
--- a/src/Mapster/Adapters/BaseClassAdapter.cs
+++ b/src/Mapster/Adapters/BaseClassAdapter.cs
@@ -220,6 +220,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
var arguments = new List();
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)
@@ -245,7 +246,9 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
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)
{
diff --git a/src/Mapster/Compile/CompileContext.cs b/src/Mapster/Compile/CompileContext.cs
index f0188fb4..72d640c8 100644
--- a/src/Mapster/Compile/CompileContext.cs
+++ b/src/Mapster/Compile/CompileContext.cs
@@ -12,6 +12,7 @@ public class CompileContext
public int? MaxDepth { get; set; }
public int Depth { get; set; }
public HashSet ExtraParameters { get; } = new();
+ public HashSet<(Expression param, CompileArgument arg)> NullChecks { get; } = new();
internal bool IsSubFunction()
{
diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs
index 090d81e1..bca07c31 100644
--- a/src/Mapster/Utils/ExpressionEx.cs
+++ b/src/Mapster/Utils/ExpressionEx.cs
@@ -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
@@ -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();