From 4414d90ab09d192d65a1408571641f46fa8b6825 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 26 Mar 2026 05:59:35 +0500 Subject: [PATCH 1/5] Bump version to 10.0.6 --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3efb4b24..199c084e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ false - 10.0.5 + 10.0.6 netstandard2.0;net10.0;net9.0;net8.0 netstandard2.0;net10.0;net9.0;net8.0 net10.0;net9.0;net8.0 From 7d452707110376dab87f51ead2e3fc307eaffb10 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 27 Mar 2026 09:28:38 +0500 Subject: [PATCH 2/5] fix(test): fix RequiredProperty test --- src/Mapster.Tests/WhenMappingRecordRegression.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs index c1bb9dad..85d291e8 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -449,11 +449,7 @@ public void RequiredProperty() { var source = new Person553 { FirstMidName = "John", LastName = "Dow" }; var destination = new Person554 { ID = 245, FirstMidName = "Mary", LastName = "Dow" }; - - TypeAdapterConfig.NewConfig() - //.Map(dest => dest.ID, source => 0) - .Ignore(x => x.ID); - + var s = source.BuildAdapter().CreateMapToTargetExpression(); var result = source.Adapt(destination); From 29b8b82aa2dc9839af91240e895f58d6c8ef5b21 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 27 Mar 2026 09:51:40 +0500 Subject: [PATCH 3/5] fix: Regression in v10 - Forced Update of Required Property #899 --- src/Mapster.Core/Enums/MapType.cs | 1 + src/Mapster/Adapters/BaseAdapter.cs | 2 +- src/Mapster/Adapters/BaseClassAdapter.cs | 3 ++- src/Mapster/Compile/CompileArgument.cs | 15 +++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Mapster.Core/Enums/MapType.cs b/src/Mapster.Core/Enums/MapType.cs index 420cc812..fa8762f1 100644 --- a/src/Mapster.Core/Enums/MapType.cs +++ b/src/Mapster.Core/Enums/MapType.cs @@ -8,5 +8,6 @@ public enum MapType Map = 1, MapToTarget = 2, Projection = 4, + ApplyNullPropagation = 8, } } \ No newline at end of file diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index b519a334..65c9d0f9 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -220,7 +220,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de .Any(y => y.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute")); if (requiremembers.Count() != 0) - set = CreateInlineExpression(source, arg, true); + set = CreateInlineExpression(source, arg.CloneWith(MapType.ApplyNullPropagation), true); else set = CreateInstantiationExpression(transformedSource, destination, arg); diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index e46c287a..f092c3ab 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -113,7 +113,8 @@ select fn(src, destinationMember, arg)) Destination = (ParameterExpression?)destination, UseDestinationValue = IsCanUsingDestinationValue(arg, destinationMember), }; - if(getter == null && !arg.DestinationType.IsRecordType() + if(arg.MapType == MapType.ApplyNullPropagation && + getter == null && !arg.DestinationType.IsRecordType() && destinationMember.Info is PropertyInfo propinfo) { if (propinfo.GetCustomAttributes() diff --git a/src/Mapster/Compile/CompileArgument.cs b/src/Mapster/Compile/CompileArgument.cs index d324661d..c2825b31 100644 --- a/src/Mapster/Compile/CompileArgument.cs +++ b/src/Mapster/Compile/CompileArgument.cs @@ -48,5 +48,20 @@ select split _fetchConstructUsing = true; return _constructUsing; } + + public CompileArgument CloneWith(MapType? mapType = null) + { + var config = new TypeAdapterConfig(); + config.Default.EnableNonPublicMembers(true); + config.Default.ShallowCopyForSameType(true); + + var str = this.BuildAdapter(config).CreateMapExpression(); + CompileArgument result = this.Adapt(config); + + if (mapType != null) + result.MapType = mapType.Value; + + return result; + } } } From ef7cede10042d75e8e7c81b93027af24aa51b5ee Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 28 Mar 2026 08:59:15 +0500 Subject: [PATCH 4/5] feat: add Nullable Ctor Checker test --- .../WhenAddCtorNullablePropagation.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/Mapster.Tests/WhenAddCtorNullablePropagation.cs 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 +} From 32426fd02d15e8ca16ad1480e62868bdf59d3981 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 28 Mar 2026 09:01:03 +0500 Subject: [PATCH 5/5] fix: #898 - add Ctor NullPropagateion improvement --- src/Mapster/Adapters/BaseClassAdapter.cs | 5 ++- src/Mapster/Compile/CompileContext.cs | 1 + src/Mapster/Utils/ExpressionEx.cs | 41 +++++++++++++++++++++++- 3 files changed, 45 insertions(+), 2 deletions(-) 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();