From a246b7a1044a267005ef8f40cb2043acd47e0540 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 29 Jul 2026 10:02:29 +0500 Subject: [PATCH 01/14] fix: replace Expression.Default to CreateDefault(arg) --- src/Mapster/Adapters/BaseAdapter.cs | 2 +- src/Mapster/Adapters/ClassAdapter.cs | 2 +- src/Mapster/TypeAdapterConfig.cs | 2 +- src/Mapster/Utils/ExpressionEx.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index f5cafc09..f10ac925 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -208,7 +208,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de /// Not create destination is abstract type if source is null if (arg.DestinationType.IsAbstract) blocks.Add(Expression.IfThen(Expression.Equal(source, Expression.Constant(null, arg.SourceType)), - Expression.Return(label, Expression.Default(arg.DestinationType)))); + Expression.Return(label, arg.DestinationType.CreateDefault(arg)))); //new TDest(); Expression transformedSource = source; diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 0f2c4bc5..2184db42 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -282,7 +282,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre static Expression CreateIncludeProjectionExpression(Expression source, CompileArgument arg) { - Expression body = Expression.Default(arg.DestinationType); + Expression body = arg.DestinationType.CreateDefault(arg); foreach (var tuple in arg.Settings.Includes) { var itemTuple = tuple; diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 8758e064..19e4dc64 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -495,7 +495,7 @@ private static LambdaExpression AdjustInheritedConverterReturnType(LambdaExpress var condition = Expression.TypeIs(tempDest, destinationType); UnaryExpression ifTrue = Expression.Convert(tempDest, destinationType); - DefaultExpression ifFalse = Expression.Default(destinationType); + Expression ifFalse = destinationType.CreateDefault(arg); ConditionalExpression conditionalExpr = Expression.Condition(condition, ifTrue, ifFalse); blockbody.Add(conditionalExpr); diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 4b9b0e18..644471d2 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -486,9 +486,9 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex // add supporting DestinationTransforms var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(adapt.Type)); if (transform != null) - return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, Expression.Default(adapt.Type))); + return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg))); - return Expression.Condition(condition, adapt, Expression.Default(adapt.Type)); + return Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg)); } public static string? GetMemberPath(this LambdaExpression lambda, bool firstLevelOnly = false, bool noError = false) From 5bcfaddcc27e45519fbdb609ee0457ef2fcf08ae Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 29 Jul 2026 11:59:55 +0500 Subject: [PATCH 02/14] fix: fix using Custom DefaultValue to Ctor --- src/Mapster/Adapters/BaseClassAdapter.cs | 2 +- src/Mapster/Utils/ExpressionEx.cs | 4 ++-- src/Mapster/Utils/ReflectionUtils.cs | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 7bcadabe..02d5356b 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -275,7 +275,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi } else getter = member.Getter - .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member), arg); + .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member), arg, member); diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 644471d2..346e36c6 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -445,7 +445,7 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter, Co return getter; } - public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg) + public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg, MemberMapping mapping) { if (getter == null) return adapt; @@ -488,7 +488,7 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex if (transform != null) return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg))); - return Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg)); + return Expression.Condition(condition, adapt, adapt.Type.CreateDefault(member:mapping)); } public static string? GetMemberPath(this LambdaExpression lambda, bool firstLevelOnly = false, bool noError = false) diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index d5051c43..96fc5ca4 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -358,11 +358,15 @@ public static bool IsPrimitiveKind(this Type type) return type == typeof(object) || type.UnwrapNullable().IsConvertible(); } - public static Expression CreateDefault(this Type type, CompileArgument? arg = null) + public static Expression CreateDefault(this Type type, CompileArgument? arg = null, MemberMapping? member = null) { if(arg !=null && arg.Settings.CustomDefaultValue != null) return arg.Settings.CustomDefaultValue; + if (member != null && member.OverrideSettings != null + && member.OverrideSettings.CustomDefaultValue != null) + return member.OverrideSettings.CustomDefaultValue; + return type.CanBeNull() ? Expression.Constant(null, type) : Expression.Constant(Activator.CreateInstance(type), type); From 44d33452679511c323a9236eb3aa126bf3c704fa Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 31 Jul 2026 09:19:16 +0500 Subject: [PATCH 03/14] feat(test): add test using custom DefaultValue in Ctor params --- .../WhenMapUsingOverrideTypesSettings.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index 0f03a13d..1de32d1b 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -82,8 +82,41 @@ public void UsingDefaultValueIsWorked() resultCDInsaiderReconfig.Data.ShouldBe(35); } + [TestMethod] + public void CustomDefaultValueIsWorkedWhenUsingAsCtorParam() + { + var config = new TypeAdapterConfig(); + + config.ForDestinationType() + .DefaultValue(x => 42); + + config. + NewConfig() + .MapUsing(dest => dest.Data, src => src.Data, cfg => + { + cfg.ReConfigurate() + .DefaultValue(x => 35); + }); + + var src = new NullableIntInsaider() { Data = null }; + + var result = src.Adapt(config); + + result.Data.ShouldBe(35); + } + #region TestClasses + public class NullableIntCtorParam + { + public NullableIntCtorParam(int? data) + { + Data = data; + } + public int? Data { get; } + } + + public class NullableIntInsaider { public int? Data { get; set; } From 748e491bc96fb0b653d337ddaa9eb374b2e25d5a Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 31 Jul 2026 11:00:59 +0500 Subject: [PATCH 04/14] fix: refactoring get ovveride settings --- .../TypeAdapterBuilderExtensions.cs | 2 +- .../TypeAdapterBuilderExtensions.cs | 2 +- src/Mapster/Adapters/BaseClassAdapter.cs | 16 +++---- .../Settings/ValueAccessingStrategy.cs | 47 +++++++++++++------ src/Mapster/TypeAdapterSettings.cs | 4 +- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs b/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs index 847d2068..362cd14e 100644 --- a/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs +++ b/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs @@ -63,7 +63,7 @@ public static ITypeAdapterBuilder EntityFromContext(this IType .Select(s => s(src, model, arg)) .FirstOrDefault(exp => exp != null)) .Where(exp => exp != null) - .Select(exp => Expression.Convert(exp, typeof(object))) + .Select(exp => Expression.Convert(exp.Exp, typeof(object))) .ToArray(); if (getters.Length != keys.Length) throw new InvalidOperationException($"Cannot get key for sourceType={arg.SourceType.Name}, destinationType={arg.DestinationType.Name}"); diff --git a/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs b/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs index f417651c..155e5dc3 100644 --- a/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs +++ b/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs @@ -69,7 +69,7 @@ public static ITypeAdapterBuilder EntityFromContext(this IType .Select(s => s(src, model, arg)) .FirstOrDefault(exp => exp != null)) .Where(exp => exp != null) - .Select(exp => Expression.Convert(exp, typeof(object))) + .Select(exp => Expression.Convert(exp.Exp, typeof(object))) .ToArray(); if (getters.Length != keys.Length) throw new InvalidOperationException($"Cannot get key for sourceType={arg.SourceType.Name}, destinationType={arg.DestinationType.Name}"); diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 02d5356b..d1e8a291 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -39,17 +39,18 @@ src is LambdaExpression lambda var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable(); if (arg.Settings.IgnoreNonMapped == true) resolvers = resolvers.Where(ValueAccessingStrategy.CustomResolvers.Contains); - var getter = (from fn in resolvers + var resolver = (from fn in resolvers from src in sources select fn(src, destinationMember, arg)) .FirstOrDefault(result => result != null); - if(getter is MemberExpression mem && mem?.Expression?.Type == source.Type) + var getter = resolver?.Exp; + var overideSettings = resolver?.Settings; + + if (getter is MemberExpression mem && mem?.Expression?.Type == source.Type) { getter = Expression.PropertyOrField(source, mem.Member.Name); } - var test = resolvers.Where(ValueAccessingStrategy.CustomResolvers.Contains); - if (arg.MapType == MapType.Projection && getter != null) { var s = new TopLevelMemberNameVisitor(); @@ -72,7 +73,7 @@ select fn(src, destinationMember, arg)) getter = (from fn in resolvers from src in sources select fn(src, destinationMember, arg)) - .FirstOrDefault(result => result != null); + .FirstOrDefault(result => result != null)?.Exp; } @@ -104,15 +105,10 @@ select fn(src, destinationMember, arg)) } - var nextIgnore = arg.Settings.Ignore.Next((ParameterExpression)source, (ParameterExpression?)destination, destinationMember.Name); var nextResolvers = arg.Settings.Resolvers.Next(arg.Settings.Ignore, (ParameterExpression)source, destinationMember.Name) .ToList(); - var overideSettings = arg.Settings.Resolvers - .Where(x => x.DestinationMemberName == destinationMember.Name && x.OvverideSettings != null) - .Select(x=>x.OvverideSettings).FirstOrDefault(); - var propertyModel = new MemberMapping { DestinationMember = destinationMember, diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index a94c7d79..47a7129d 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -5,7 +5,7 @@ using System.Reflection; using Mapster.Models; using Mapster.Utils; -using ValueAccess = System.Func; +using ValueAccess = System.Func; namespace Mapster { @@ -24,12 +24,13 @@ public static class ValueAccessingStrategy CustomResolverForDictionary, }; - private static Expression? CustomResolverFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? CustomResolverFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { var config = source.Type == arg.SourceType ? arg.Settings : arg.Context.Config.GetMergedSettings(new TypeTuple(source.Type, arg.DestinationType),arg.MapType); var resolvers = config.Resolvers; if (resolvers.Count == 0) return null; + TypeAdapterSettings? customSettings = null; var invokes = new List>(); @@ -39,6 +40,9 @@ public static class ValueAccessingStrategy if (!destinationMember.Name.Equals(resolver.DestinationMemberName, StringComparison.InvariantCultureIgnoreCase)) continue; + if(resolver.OvverideSettings != null && customSettings == null) + customSettings = resolver.OvverideSettings; + var invoke = resolver.GetInvokingExpression(source, arg.MapType); var condition = resolver.GetConditionExpression(source, arg.MapType); if (condition == null) @@ -66,22 +70,30 @@ public static class ValueAccessingStrategy } } - return getter; + if (getter == null) + return null; + return new ResolverResult(getter,customSettings); } - private static Expression? PropertyOrFieldFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? PropertyOrFieldFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { var members = source.Type.GetFieldsAndProperties(true); var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); - return members + var resolver = members .Where(member => member.ShouldMapMember(arg, MemberSide.Source)) .Where(member => member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter, arg) == destinationMemberName) .Select(member => member.GetExpression(source)) .FirstOrDefault(); + + if (resolver == null) + return null; + else + return new ResolverResult(resolver, null); + } - private static Expression? GetMethodFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? GetMethodFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { if (arg.MapType == MapType.Projection) return null; @@ -92,14 +104,17 @@ public static class ValueAccessingStrategy return null; if (getMethod.Name == "GetType" && destinationMember.Type != typeof(Type)) return null; - return Expression.Call(source, getMethod); + return new ResolverResult( Expression.Call(source, getMethod),null); } - private static Expression? FlattenMemberFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? FlattenMemberFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); - return GetDeepFlattening(source, destinationMemberName, arg); + var resolver = GetDeepFlattening(source, destinationMemberName, arg); + if(resolver == null) + return null; + return new ResolverResult(resolver, null); } private static Expression? GetDeepFlattening(Expression source, string propertyName, CompileArgument arg) @@ -177,7 +192,7 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM } } - private static Expression? DictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? DictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { var dictType = source.Type.GetDictionaryType(); if (dictType == null) @@ -192,18 +207,20 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM var method = typeof(MapsterHelper).GetMethods() .First(m => m.Name == nameof(MapsterHelper.FlexibleGet) && m.GetParameters()[0].ParameterType.Name == dictType.Name) .MakeGenericMethod(args[1]); - return Expression.Call(method, source.To(dictType), key, ExpressionEx.GetNameConverterExpression(strategy.SourceMemberNameConverter)); + var resolver = Expression.Call(method, source.To(dictType), key, ExpressionEx.GetNameConverterExpression(strategy.SourceMemberNameConverter)); + return new ResolverResult(resolver); } else { var method = typeof(MapsterHelper).GetMethods() .First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name) .MakeGenericMethod(args); - return Expression.Call(method, source.To(dictType), key); + var resolver = Expression.Call(method, source.To(dictType), key); + return new ResolverResult(resolver); } } - private static Expression? CustomResolverForDictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? CustomResolverForDictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { var config = arg.Settings; var resolvers = config.Resolvers; @@ -236,7 +253,9 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM } if (lastCondition != null) getter = Expression.Condition(lastCondition, getter!, getter!.Type.CreateDefault(arg)); - return getter; + return new ResolverResult(getter); } } + + public record ResolverResult(Expression Exp , TypeAdapterSettings? Settings = null); } diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs index f72a25a6..0fd70d6a 100644 --- a/src/Mapster/TypeAdapterSettings.cs +++ b/src/Mapster/TypeAdapterSettings.cs @@ -134,9 +134,9 @@ public Dictionary ProjectToTypeResolvers { get => Get(nameof(ShouldMapMember), () => new List>()); } - public List> ValueAccessingStrategies + public List> ValueAccessingStrategies { - get => Get(nameof(ValueAccessingStrategies), () => new List>()); + get => Get(nameof(ValueAccessingStrategies), () => new List>()); } public List Resolvers { From 2edfbf9c1fa0cee922b8fdd5f580f4f346a4b872 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 31 Jul 2026 12:13:38 +0500 Subject: [PATCH 05/14] feat: add ExtraSource custom Settings Supported --- .../TypeAdapterBuilderExtensions.cs | 2 +- .../TypeAdapterBuilderExtensions.cs | 2 +- src/Mapster/Adapters/BaseClassAdapter.cs | 7 ++-- src/Mapster/Models/ExtraSourceModel.cs | 10 ++++++ .../Settings/ValueAccessingStrategy.cs | 36 ++++++++++++++----- src/Mapster/TypeAdapterSetter.cs | 8 ++--- src/Mapster/TypeAdapterSettings.cs | 8 ++--- 7 files changed, 49 insertions(+), 24 deletions(-) create mode 100644 src/Mapster/Models/ExtraSourceModel.cs diff --git a/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs b/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs index 362cd14e..70dfee7b 100644 --- a/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs +++ b/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs @@ -60,7 +60,7 @@ public static ITypeAdapterBuilder EntityFromContext(this IType var getters = keys.Select(key => arg.DestinationType.GetProperty(key)) .Select(prop => new PropertyModel(prop)) .Select(model => arg.Settings.ValueAccessingStrategies - .Select(s => s(src, model, arg)) + .Select(s => s((ResolverSourceInput)src, model, arg)) .FirstOrDefault(exp => exp != null)) .Where(exp => exp != null) .Select(exp => Expression.Convert(exp.Exp, typeof(object))) diff --git a/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs b/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs index 155e5dc3..88a207a5 100644 --- a/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs +++ b/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs @@ -66,7 +66,7 @@ public static ITypeAdapterBuilder EntityFromContext(this IType var getters = keys.Select(key => arg.DestinationType.GetProperty(key)) .Select(prop => new PropertyModel(prop!)) .Select(model => arg.Settings.ValueAccessingStrategies - .Select(s => s(src, model, arg)) + .Select(s => s((ResolverSourceInput)src, model, arg)) .FirstOrDefault(exp => exp != null)) .Where(exp => exp != null) .Select(exp => Expression.Convert(exp.Exp, typeof(object))) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index d1e8a291..0ed61e37 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -25,12 +25,9 @@ protected ClassMapping CreateClassConverter(Expression source, ClassModel classM if (arg.Settings.IgnoreNonMapped == true) IgnoreNonMapped(classModel,arg); - var sources = new List {source}; + var sources = new List {new ResolverSourceInput(source)}; sources.AddRange( - arg.Settings.ExtraSources.Select(src => - src is LambdaExpression lambda - ? lambda.Apply(arg.MapType, source) - : ExpressionEx.PropertyOrFieldPath(source, (string)src))); + arg.Settings.ExtraSources.Select(src => ResolverSourceInput.ConvertFrom(src,source,arg))); foreach (var destinationMember in destinationMembers) { if (ProcessIgnores(arg, destinationMember, out var ignore) && !ctorMapping) diff --git a/src/Mapster/Models/ExtraSourceModel.cs b/src/Mapster/Models/ExtraSourceModel.cs new file mode 100644 index 00000000..94469111 --- /dev/null +++ b/src/Mapster/Models/ExtraSourceModel.cs @@ -0,0 +1,10 @@ +using System.Linq.Expressions; + +namespace Mapster.Models +{ + public record ExtraSourceModel(object Src, OverrideTypesSettings? Settings = null) + { + public static explicit operator ExtraSourceModel(Expression src) => new ExtraSourceModel(src); + public static explicit operator ExtraSourceModel(string src) => new ExtraSourceModel(src); + } +} diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index 47a7129d..c1d4a90c 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -5,7 +5,7 @@ using System.Reflection; using Mapster.Models; using Mapster.Utils; -using ValueAccess = System.Func; +using ValueAccess = System.Func; namespace Mapster { @@ -24,8 +24,9 @@ public static class ValueAccessingStrategy CustomResolverForDictionary, }; - private static ResolverResult? CustomResolverFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? CustomResolverFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var config = source.Type == arg.SourceType ? arg.Settings : arg.Context.Config.GetMergedSettings(new TypeTuple(source.Type, arg.DestinationType),arg.MapType); var resolvers = config.Resolvers; if (resolvers.Count == 0) @@ -72,11 +73,12 @@ public static class ValueAccessingStrategy if (getter == null) return null; - return new ResolverResult(getter,customSettings); + return new ResolverResult(getter,(OverrideTypesSettings?)customSettings); } - private static ResolverResult? PropertyOrFieldFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? PropertyOrFieldFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var members = source.Type.GetFieldsAndProperties(true); var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); @@ -93,8 +95,9 @@ public static class ValueAccessingStrategy } - private static ResolverResult? GetMethodFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? GetMethodFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; if (arg.MapType == MapType.Projection) return null; var strategy = arg.Settings.NameMatchingStrategy; @@ -107,8 +110,9 @@ public static class ValueAccessingStrategy return new ResolverResult( Expression.Call(source, getMethod),null); } - private static ResolverResult? FlattenMemberFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? FlattenMemberFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); var resolver = GetDeepFlattening(source, destinationMemberName, arg); @@ -192,8 +196,9 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM } } - private static ResolverResult? DictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? DictionaryFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var dictType = source.Type.GetDictionaryType(); if (dictType == null) return null; @@ -220,8 +225,9 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM } } - private static ResolverResult? CustomResolverForDictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? CustomResolverForDictionaryFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var config = arg.Settings; var resolvers = config.Resolvers; if (resolvers.Count == 0) @@ -257,5 +263,17 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM } } - public record ResolverResult(Expression Exp , TypeAdapterSettings? Settings = null); + public record ResolverResult(Expression Exp , OverrideTypesSettings? Settings = null); + public record ResolverSourceInput(Expression Src, OverrideTypesSettings? Settings = null) + { + public static explicit operator ResolverSourceInput(Expression src) => new ResolverSourceInput(src); + public static explicit operator ResolverSourceInput(ParameterExpression src) => new ResolverSourceInput(src); + public static ResolverSourceInput ConvertFrom(ExtraSourceModel extraSource,Expression source, CompileArgument arg) + { + if (extraSource.Src is LambdaExpression lambda) + return new ResolverSourceInput(lambda.Apply(arg.MapType, source), extraSource.Settings); + else + return new ResolverSourceInput(ExpressionEx.PropertyOrFieldPath(source, (string)extraSource.Src), extraSource.Settings); + } + }; } diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 2577cde5..f52e20c5 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -431,7 +431,7 @@ public TypeAdapterSetter Map( var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof (object))); if (member.IsIdentity()) { - Settings.ExtraSources.Add(invoker); + Settings.ExtraSources.Add((ExtraSourceModel)invoker); return this; } @@ -452,7 +452,7 @@ public TypeAdapterSetter Map( if (destinationMember.IsIdentity()) { - Settings.ExtraSources.Add(sourceMemberName); + Settings.ExtraSources.Add((ExtraSourceModel)sourceMemberName); return this; } @@ -648,7 +648,7 @@ public TypeAdapterSetter MapUsing Map ProjectToTypeResolvers { get => Get(nameof(ShouldMapMember), () => new List>()); } - public List> ValueAccessingStrategies + public List> ValueAccessingStrategies { - get => Get(nameof(ValueAccessingStrategies), () => new List>()); + get => Get(nameof(ValueAccessingStrategies), () => new List>()); } public List Resolvers { get => Get(nameof(Resolvers), () => new List()); } - public List ExtraSources + public List ExtraSources { - get => Get(nameof(ExtraSources), () => new List()); + get => Get(nameof(ExtraSources), () => new List()); } public List> BeforeMappingFactories { From 2d64de767e062e1d48a9e877293915b17672c7ab Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 3 Aug 2026 12:03:19 +0500 Subject: [PATCH 06/14] fix: drop Ctor param NullPropagation overhead --- src/Mapster.Core/Enums/MapType.cs | 1 + src/Mapster/Adapters/BaseAdapter.cs | 8 ++++++-- src/Mapster/Adapters/BaseClassAdapter.cs | 5 +++-- src/Mapster/Compile/CompileContext.cs | 4 +++- src/Mapster/Utils/ExpressionEx.cs | 13 +++++++++---- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Mapster.Core/Enums/MapType.cs b/src/Mapster.Core/Enums/MapType.cs index fa8762f1..8c0646bc 100644 --- a/src/Mapster.Core/Enums/MapType.cs +++ b/src/Mapster.Core/Enums/MapType.cs @@ -9,5 +9,6 @@ public enum MapType MapToTarget = 2, Projection = 4, ApplyNullPropagation = 8, + CtorParam = 16, } } \ No newline at end of file diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index f10ac925..cf2c212d 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -388,6 +388,9 @@ private static Expression InvokeMapping( if (exp == null) return null; + if(arg.MapType == MapType.CtorParam) + return exp; + //projection null is handled by EF if (arg.MapType != MapType.Projection) exp = source.NotNullReturn(exp,arg); @@ -448,9 +451,10 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex } } - internal static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null) + internal static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null, MapType? mapTypeCtor = null) { - var mapType = arg.MapType == MapType.MapToTarget && destination == null ? MapType.Map : + var mapType = mapTypeCtor != null ? mapTypeCtor.Value: + arg.MapType == MapType.MapToTarget && destination == null ? MapType.Map : mapping?.UseDestinationValue == true ? MapType.MapToTarget : arg.MapType; var extraParams = new HashSet(); diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 0ed61e37..8aec7034 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -222,7 +222,8 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi var members = classConverter.Members; var arguments = new List(); - arg.Context.NullChecks.UnionWith(members.Where(x => x.Getter != null).Select(x => (x.Getter, arg))); + // ReadyToCleanUp + // arg.Context.NullChecks.UnionWith(members.Where(x => x.Getter != null).Select(x => (x.Getter, arg))); foreach (var member in members) { var parameterInfo = (ParameterInfo)member.DestinationMember.Info!; @@ -268,7 +269,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi } else getter = member.Getter - .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member), arg, member); + .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member,mapTypeCtor:MapType.CtorParam), arg, member); diff --git a/src/Mapster/Compile/CompileContext.cs b/src/Mapster/Compile/CompileContext.cs index 72d640c8..0c21abc7 100644 --- a/src/Mapster/Compile/CompileContext.cs +++ b/src/Mapster/Compile/CompileContext.cs @@ -12,7 +12,9 @@ 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(); + + // ReadyToCleanUp + // 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 346e36c6..1da39818 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -452,9 +452,10 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex Expression? condition = null; var current = getter; - var checks = arg.Context.NullChecks - .Where(x => !object.ReferenceEquals(x.arg, arg)) - .Select(x => x.param); + // ReadyToCleanUp + //var checks = arg.Context.NullChecks + // .Where(x => !object.ReferenceEquals(x.arg, arg)) + // .Select(x => x.param); while (current != null) { @@ -462,8 +463,12 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex if (current.CanBeNull() && current is not ParameterExpression) compareNull = Expression.NotEqual(current, Expression.Constant(null, current.Type)); + // ReadyToCleanUp + //else if (current.CanBeNull() && current is ParameterExpression param + // && !checks.Contains(param)) else if (current.CanBeNull() && current is ParameterExpression param - && !checks.Contains(param)) + && arg.MapType == MapType.Projection) + compareNull = Expression.NotEqual(param, Expression.Constant(null, param.Type)); if (compareNull != null) From 3acfd829657e7abdc107ec1f9efdbebced49c4c0 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 4 Aug 2026 11:38:01 +0500 Subject: [PATCH 07/14] fix: add replacer to ExtraSource params --- .../WhenMapUsingOverrideTypesSettings.cs | 37 +++++++++++++++++++ .../Settings/ValueAccessingStrategy.cs | 2 +- src/Mapster/TypeAdapterSetter.cs | 14 +++---- src/Mapster/Utils/ExpressionEx.cs | 14 +++++++ .../Utils/ParameterExpressionReplacer.cs | 21 ++++++++++- 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index 1de32d1b..5029d4cc 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -105,8 +105,45 @@ public void CustomDefaultValueIsWorkedWhenUsingAsCtorParam() result.Data.ShouldBe(35); } + [TestMethod] + public void ExtraSourceUsingCustomConfig() + { + var config = new TypeAdapterConfig(); + config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + config.NewConfig() + .MapUsing(dest=> dest, src => src.SrcData, cfg => + { + cfg.SkipDestinationTransforms(); + }); + + var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello" } }; + + var result = src.Adapt(config); + + } + + #region TestClasses + public class DestinationFlattentData + { + public int Data { get; set; } + public string Value { get; set; } + public List Collection { get; set; } + } + + public class SourceFlattentData + { + public int Data { get; set; } + public string Value { get; set; } + public List Collection { get; set; } + } + + public class SourceFlattentInsaider + { + public SourceFlattentData SrcData { get; set; } + } + public class NullableIntCtorParam { public NullableIntCtorParam(int? data) diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index c1d4a90c..0cbd9459 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -271,7 +271,7 @@ public record ResolverSourceInput(Expression Src, OverrideTypesSettings? Setting public static ResolverSourceInput ConvertFrom(ExtraSourceModel extraSource,Expression source, CompileArgument arg) { if (extraSource.Src is LambdaExpression lambda) - return new ResolverSourceInput(lambda.Apply(arg.MapType, source), extraSource.Settings); + return new ResolverSourceInput(lambda.ApplyExtraSources(arg.MapType, source), extraSource.Settings); else return new ResolverSourceInput(ExpressionEx.PropertyOrFieldPath(source, (string)extraSource.Src), extraSource.Settings); } diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index f52e20c5..2852132a 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -645,13 +645,7 @@ public TypeAdapterSetter MapUsing MapUsing n > 1 && exps[i].IsComplex()).Any()) + return result!; + return Expression.Invoke(lambda, exps); + } + public static LambdaExpression TrimParameters(this LambdaExpression lambda, int skip = 0) { var replacer = new ParameterExpressionReplacer(lambda.Parameters, lambda.Parameters.ToArray()); diff --git a/src/Mapster/Utils/ParameterExpressionReplacer.cs b/src/Mapster/Utils/ParameterExpressionReplacer.cs index 16c02465..f1a0ee91 100644 --- a/src/Mapster/Utils/ParameterExpressionReplacer.cs +++ b/src/Mapster/Utils/ParameterExpressionReplacer.cs @@ -8,10 +8,20 @@ sealed class ParameterExpressionReplacer : ExpressionVisitor //fields readonly ReadOnlyCollection _from; readonly Expression[] _to; + readonly bool _FromExtraSource; public int[] ReplaceCounts { get; } //constructors + + public ParameterExpressionReplacer(ReadOnlyCollection from,bool isExtraSource, params Expression[] to ) + { + _from = from; + _to = to; + ReplaceCounts = new int[_to.Length]; + _FromExtraSource = isExtraSource; + } + public ParameterExpressionReplacer(ReadOnlyCollection from, params Expression[] to) { _from = from; @@ -24,7 +34,16 @@ protected override Expression VisitParameter(ParameterExpression node) for (var i = 0; i < _from.Count; i++) { if (node != _from[i]) - continue; + { + if (_FromExtraSource) + { + if (node.Type != _from[i].Type) + continue; + } + else + continue; + } + if (i >= _to.Length) return node.Type.CreateDefault(); From 44c804559b679e934969dbbb17a4f4daaff0ffcf Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 5 Aug 2026 10:02:14 +0500 Subject: [PATCH 08/14] fix: src param replace --- src/Mapster/Adapters/BaseClassAdapter.cs | 7 ++-- src/Mapster/Utils/ParametrExpressionFinder.cs | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/Mapster/Utils/ParametrExpressionFinder.cs diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 8aec7034..86e8e75d 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -43,10 +43,9 @@ select fn(src, destinationMember, arg)) var getter = resolver?.Exp; var overideSettings = resolver?.Settings; - if (getter is MemberExpression mem && mem?.Expression?.Type == source.Type) - { - getter = Expression.PropertyOrField(source, mem.Member.Name); - } + // source in overideSettings is not source in this context + if (overideSettings != null && getter != null) + getter = ReplaceOvverideExpressionParam.Replace(getter, source); if (arg.MapType == MapType.Projection && getter != null) { diff --git a/src/Mapster/Utils/ParametrExpressionFinder.cs b/src/Mapster/Utils/ParametrExpressionFinder.cs new file mode 100644 index 00000000..c9e0ab13 --- /dev/null +++ b/src/Mapster/Utils/ParametrExpressionFinder.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq.Expressions; + +namespace Mapster.Utils +{ + sealed internal class ParametrExpressionFinder: ExpressionVisitor + { + private readonly List _parameters = new(); + + protected override Expression VisitParameter(ParameterExpression node) + { + if (!_parameters.Contains(node)) + _parameters.Add(node); + + return base.VisitParameter(node); + } + + public ReadOnlyCollection Find(Expression expression) + { + _parameters.Clear(); + this.Visit(expression); + return _parameters.AsReadOnly(); + } + } + + internal static class ReplaceOvverideExpressionParam + { + readonly static ParametrExpressionFinder ParamFinder = new (); + + public static Expression Replace(Expression expression, params Expression[] to) + { + return new ParameterExpressionReplacer(ParamFinder.Find(expression), true, to).Visit(expression); + } + } +} From 78bafc1497afc2583f8aa8f225605e9ef653f7b5 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 5 Aug 2026 10:40:46 +0500 Subject: [PATCH 09/14] feat: add ApplyResolversOnly --- src/Mapster/Models/InvokerModel.cs | 19 +++++++++++++++++-- .../Settings/ValueAccessingStrategy.cs | 2 +- .../OverrideTypesSettings.cs | 19 ++++++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Mapster/Models/InvokerModel.cs b/src/Mapster/Models/InvokerModel.cs index f2201c3d..c45ee60a 100644 --- a/src/Mapster/Models/InvokerModel.cs +++ b/src/Mapster/Models/InvokerModel.cs @@ -1,5 +1,6 @@ -using System.Linq.Expressions; -using Mapster.Utils; +using Mapster.Utils; +using System.Collections.Generic; +using System.Linq.Expressions; namespace Mapster.Models { @@ -47,4 +48,18 @@ public Expression GetInvokingExpression(Expression exp, MapType mapType = MapTyp : Condition?.Apply(mapType, exp); } } + + public class InvokerModelApplyComparer : IEqualityComparer + { + public bool Equals(InvokerModel? x, InvokerModel? y) + { + if (x is null || y is null) return false; + return string.Equals(x.DestinationMemberName, y.DestinationMemberName, System.StringComparison.InvariantCulture); + } + + public int GetHashCode(InvokerModel obj) + { + return obj?.DestinationMemberName?.GetHashCode() ?? 0; + } + } } \ No newline at end of file diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index 0cbd9459..6b4be699 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -28,7 +28,7 @@ public static class ValueAccessingStrategy { var source = srcInput.Src; var config = source.Type == arg.SourceType ? arg.Settings : arg.Context.Config.GetMergedSettings(new TypeTuple(source.Type, arg.DestinationType),arg.MapType); - var resolvers = config.Resolvers; + var resolvers = srcInput.Settings != null ? srcInput.Settings.ApplyResolversOnly(config) : config.Resolvers; if (resolvers.Count == 0) return null; TypeAdapterSettings? customSettings = null; diff --git a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs index ebb160c4..50748f6a 100644 --- a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs +++ b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Mapster.Models; +using System.Collections.Generic; +using System.Linq; namespace Mapster { @@ -27,5 +29,20 @@ public override void Apply(SettingStore other) if(!SkipAllSettings.GetValueOrDefault()) base.ApplyWithSkipSettings(other, SkipSettings); } + + public List ApplyResolversOnly(TypeAdapterSettings other) + { + var result = new List(this.Resolvers); + var seen = new HashSet(result,new InvokerModelApplyComparer()); + + foreach (var item in other.Resolvers) + { + if (seen.Add(item)) + { + result.Add(item); + } + } + return result; + } } } From 2f4c5d6b2f23a152b779893400e1766daf5224d5 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 5 Aug 2026 12:09:27 +0500 Subject: [PATCH 10/14] fix: extra params apply --- .../WhenMapUsingOverrideTypesSettings.cs | 12 +++++++++++- src/Mapster/Adapters/BaseClassAdapter.cs | 5 +++-- src/Mapster/Models/InvokerModel.cs | 8 ++++---- src/Mapster/Settings/ValueAccessingStrategy.cs | 2 +- .../TypeAdapterSettings/OverrideTypesSettings.cs | 5 +++++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index 5029d4cc..b28c4c47 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -113,13 +113,23 @@ public void ExtraSourceUsingCustomConfig() config.NewConfig() .MapUsing(dest=> dest, src => src.SrcData, cfg => { - cfg.SkipDestinationTransforms(); + cfg.SkipDestinationTransforms() + .ReConfigurate() + .Map(dest=>dest.Data, src => 42) + .MapUsing(dest => dest.Collection, src => src.Collection, cfg => + { + cfg + .SkipDestinationTransforms(); + }) + ; }); var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello" } }; var result = src.Adapt(config); + result.Collection.ShouldBeNull(); + result.Data.ShouldBe(42); } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 86e8e75d..88cfbaae 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -43,9 +43,10 @@ select fn(src, destinationMember, arg)) var getter = resolver?.Exp; var overideSettings = resolver?.Settings; + // ReadyToCleanUp // source in overideSettings is not source in this context - if (overideSettings != null && getter != null) - getter = ReplaceOvverideExpressionParam.Replace(getter, source); + // if (overideSettings != null && getter != null) + // getter = ReplaceOvverideExpressionParam.Replace(getter, source); if (arg.MapType == MapType.Projection && getter != null) { diff --git a/src/Mapster/Models/InvokerModel.cs b/src/Mapster/Models/InvokerModel.cs index c45ee60a..bad43ecc 100644 --- a/src/Mapster/Models/InvokerModel.cs +++ b/src/Mapster/Models/InvokerModel.cs @@ -32,20 +32,20 @@ public class InvokerModel }; } - public Expression GetInvokingExpression(Expression exp, MapType mapType = MapType.Map) + public Expression GetInvokingExpression(Expression exp, MapType mapType = MapType.Map, bool isExtraParam = false) { if (IsChildPath) return Invoker!.Body; return SourceMemberName != null ? ExpressionEx.PropertyOrFieldPath(exp, SourceMemberName) - : Invoker!.Apply(mapType, exp); + : isExtraParam ? Invoker!.ApplyExtraSources(mapType, exp) : Invoker!.Apply(mapType, exp); } - public Expression? GetConditionExpression(Expression exp, MapType mapType = MapType.Map) + public Expression? GetConditionExpression(Expression exp, MapType mapType = MapType.Map, bool isExtraParam = false) { return IsChildPath ? Condition?.Body - : Condition?.Apply(mapType, exp); + : isExtraParam ? Condition?.ApplyExtraSources(mapType, exp) : Condition?.Apply(mapType, exp); } } diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index 6b4be699..391776b3 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -44,7 +44,7 @@ public static class ValueAccessingStrategy if(resolver.OvverideSettings != null && customSettings == null) customSettings = resolver.OvverideSettings; - var invoke = resolver.GetInvokingExpression(source, arg.MapType); + var invoke = resolver.GetInvokingExpression(source, arg.MapType, customSettings != null); var condition = resolver.GetConditionExpression(source, arg.MapType); if (condition == null) { diff --git a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs index 50748f6a..5eae8e44 100644 --- a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs +++ b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs @@ -12,6 +12,11 @@ public List SkipSettings get => Get(nameof(SkipSettings), () => new List()); } + public IEnumerable ReMapDestination + { + get => this.Resolvers.Select(x=>x.DestinationMemberName); + } + public bool? SkipAllSettings { get => Get(nameof(SkipAllSettings)); From b5887cf246a380bc411ee8d626cb3d8f7687a6ab Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 5 Aug 2026 14:17:14 +0500 Subject: [PATCH 11/14] feat: add Remap Setter --- .../WhenMapUsingOverrideTypesSettings.cs | 22 +++++++++++ src/Mapster/Adapters/BaseClassAdapter.cs | 18 +++++++-- .../Settings/ValueAccessingStrategy.cs | 2 +- src/Mapster/TypeAdapterSetter.cs | 39 +++++++++++++++++++ .../TypeAdapterSetters/OverrideTypesSetter.cs | 2 +- .../OverrideTypesSettings.cs | 18 +++++++++ 6 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index b28c4c47..50439980 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -133,6 +133,28 @@ public void ExtraSourceUsingCustomConfig() } + [TestMethod] + public void ReMapSettersIsWorked() + { + var config = new TypeAdapterConfig(); + config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + config.ForDestinationType() + .Ignore(x => x.Value); + config.NewConfig() + .ReMap(dest => dest, src => src.SrcData, true); + + + var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello" } }; + + //var str = src.BuildAdapter(config).CreateMapExpression(); + + var result = src.Adapt(config); + + result.Collection.ShouldBeNull(); + result.Value.ShouldBe("Hello"); + + } + #region TestClasses public class DestinationFlattentData diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 88cfbaae..ea5e9f53 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -30,7 +30,7 @@ protected ClassMapping CreateClassConverter(Expression source, ClassModel classM arg.Settings.ExtraSources.Select(src => ResolverSourceInput.ConvertFrom(src,source,arg))); foreach (var destinationMember in destinationMembers) { - if (ProcessIgnores(arg, destinationMember, out var ignore) && !ctorMapping) + if (!destinationMember.ShouldMapMember(arg, MemberSide.Destination)) continue; var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable(); @@ -43,6 +43,9 @@ select fn(src, destinationMember, arg)) var getter = resolver?.Exp; var overideSettings = resolver?.Settings; + if (ProcessIgnores(arg, destinationMember,out var ignore, resolver) && !ctorMapping) + continue; + // ReadyToCleanUp // source in overideSettings is not source in this context // if (overideSettings != null && getter != null) @@ -206,10 +209,19 @@ protected static bool IsCanUsingDestinationValue(CompileArgument arg, IMemberMod protected static bool ProcessIgnores( CompileArgument arg, - IMemberModel destinationMember, - out IgnoreDictionary.IgnoreItem ignore) + IMemberModel destinationMember, + out IgnoreDictionary.IgnoreItem ignore, + ResolverResult? resolver = null) { ignore = new IgnoreDictionary.IgnoreItem(); + + if (resolver?.Settings != null) + { + if(resolver.Settings.RemapExtraSource.GetValueOrDefault() + || resolver.Settings.ReMapDestination.Contains(destinationMember.Name)) + return false; + } + if (!destinationMember.ShouldMapMember(arg, MemberSide.Destination)) return true; diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index 391776b3..c045d750 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -91,7 +91,7 @@ public static class ValueAccessingStrategy if (resolver == null) return null; else - return new ResolverResult(resolver, null); + return new ResolverResult(resolver, srcInput.Settings != null ? srcInput.Settings.CloneOnlySkipSettings() : null); } diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 2852132a..24dbcfe8 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -672,6 +672,45 @@ public TypeAdapterSetter MapUsing ReMap( + Expression> member, + Expression> source, + bool SkipDestinationTransforms = false) + { + this.CheckCompiled(); + + + + var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof(TSource))); + TypeAdapterSettings? overrideSettings = null; + + var Tempsetter = new OverrideTypesSetter(this.Config); + overrideSettings = Tempsetter.Settings; + + + if (SkipDestinationTransforms) + Tempsetter.SkipDestinationTransforms(); + + if (member.IsIdentity()) + { + Tempsetter._Settings.RemapExtraSource = true; + + Settings.ExtraSources.Add(new ExtraSourceModel(invoker, (OverrideTypesSettings?)overrideSettings)); + return this; + } + + this.IgnoredRemove(member.GetMemberPath()!); + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = member.GetMemberPath()!, + Invoker = invoker, + Condition = null, + OvverideSettings = overrideSettings + }); + return this; + } + public TypeAdapterSetter IgnoreIf( diff --git a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs index a6254f4d..c27eb989 100644 --- a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs +++ b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs @@ -7,7 +7,7 @@ namespace Mapster [AdaptWith(AdaptDirectives.DestinationAsRecord)] public class OverrideTypesSetter : TypeAdapterSetter { - protected OverrideTypesSettings _Settings { get => (OverrideTypesSettings)Settings; } + internal protected OverrideTypesSettings _Settings { get => (OverrideTypesSettings)Settings; } public OverrideTypesSetter(TypeAdapterConfig config) : this (new OverrideTypesSettings (), config) { } public OverrideTypesSetter(TypeAdapterSettings settings, TypeAdapterConfig config) : base(settings, config) { } diff --git a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs index 5eae8e44..b5223f01 100644 --- a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs +++ b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs @@ -1,4 +1,5 @@ using Mapster.Models; +using System; using System.Collections.Generic; using System.Linq; @@ -17,6 +18,12 @@ public IEnumerable ReMapDestination get => this.Resolvers.Select(x=>x.DestinationMemberName); } + public bool? RemapExtraSource + { + get => Get(nameof(RemapExtraSource)); + set => Set(nameof(RemapExtraSource), value); + } + public bool? SkipAllSettings { get => Get(nameof(SkipAllSettings)); @@ -49,5 +56,16 @@ public List ApplyResolversOnly(TypeAdapterSettings other) } return result; } + + public OverrideTypesSettings CloneOnlySkipSettings() + { + var result = new OverrideTypesSettings(); + + result.SkipAllSettings = this.SkipAllSettings; + result.SkipSettings.AddRange(this.SkipSettings); + result.RemapExtraSource = this.RemapExtraSource; + + return result; + } } } From 854f74a35c1dd00293060900eccc16aef7ab1889 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 6 Aug 2026 11:40:17 +0500 Subject: [PATCH 12/14] fix: refactoring ReMap setter --- .../WhenMapUsingOverrideTypesSettings.cs | 22 +++++++++++++++---- src/Mapster/Adapters/BaseClassAdapter.cs | 5 +++-- src/Mapster/TypeAdapterSetter.cs | 4 ++-- src/Mapster/TypeAdapterSettings.cs | 5 +++++ .../OverrideTypesSettings.cs | 10 ++++----- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index 50439980..e175adb1 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -139,24 +139,38 @@ public void ReMapSettersIsWorked() var config = new TypeAdapterConfig(); config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); config.ForDestinationType() - .Ignore(x => x.Value); + .Ignore(x => x.Value) + .Ignore(x => x.Data); config.NewConfig() .ReMap(dest => dest, src => src.SrcData, true); + config.NewConfig() + .ReMap(dest => dest.Data, src => src.Data); + var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello", Data = 42 } }; + var reMapSrc = new RemapMemberMappings { Data = 21, Value = "World" }; - var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello" } }; - //var str = src.BuildAdapter(config).CreateMapExpression(); var result = src.Adapt(config); result.Collection.ShouldBeNull(); result.Value.ShouldBe("Hello"); - + result.Data.ShouldBe(42); + + var reMapResut = reMapSrc.Adapt(config); + + reMapResut.Data.ShouldBe(21); + reMapResut.Value.ShouldBe(default); } #region TestClasses + public class RemapMemberMappings + { + public int Data { get; set; } + public string Value { get; set; } + } + public class DestinationFlattentData { public int Data { get; set; } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index ea5e9f53..a1888d38 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -217,8 +217,9 @@ protected static bool ProcessIgnores( if (resolver?.Settings != null) { - if(resolver.Settings.RemapExtraSource.GetValueOrDefault() - || resolver.Settings.ReMapDestination.Contains(destinationMember.Name)) + if(resolver.Settings.ReMapExtraSource.GetValueOrDefault() + || resolver.Settings.ReMapDestination.Contains(destinationMember.Name) + || arg.Settings.ReMapDestinationMembers.Contains(destinationMember.Name)) return false; } diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 24dbcfe8..01d4bbae 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -693,13 +693,13 @@ public TypeAdapterSetter ReMap Set(nameof(CustomDefaultValue), value); } + public List ReMapDestinationMembers + { + get => Get(nameof(ReMapDestinationMembers), () => new List()); + } + internal bool Compiled { get; set; } public TypeAdapterSettings Clone() diff --git a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs index b5223f01..359b13cf 100644 --- a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs +++ b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs @@ -15,13 +15,13 @@ public List SkipSettings public IEnumerable ReMapDestination { - get => this.Resolvers.Select(x=>x.DestinationMemberName); + get => this.Resolvers.Select(x=>x.DestinationMemberName).Union(ReMapDestinationMembers); } - public bool? RemapExtraSource + public bool? ReMapExtraSource { - get => Get(nameof(RemapExtraSource)); - set => Set(nameof(RemapExtraSource), value); + get => Get(nameof(ReMapExtraSource)); + set => Set(nameof(ReMapExtraSource), value); } public bool? SkipAllSettings @@ -63,7 +63,7 @@ public OverrideTypesSettings CloneOnlySkipSettings() result.SkipAllSettings = this.SkipAllSettings; result.SkipSettings.AddRange(this.SkipSettings); - result.RemapExtraSource = this.RemapExtraSource; + result.ReMapExtraSource = this.ReMapExtraSource; return result; } From f3e348e49fb8a9e55bd5c8df6d6ca25ae37768ac Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 6 Aug 2026 15:22:12 +0500 Subject: [PATCH 13/14] fix: custom default --- src/Mapster/Utils/ExpressionEx.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 8990f07b..0742852c 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -438,10 +438,10 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter, Co if (!getter.CanBeNull()) { var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type)); - return Expression.Condition(condition, transform, transform.Type.CreateDefault(arg)); + return Expression.Condition(condition, transform, transform.Type.CreateDefault()); } else - return Expression.Condition(condition, getter, getter.Type.CreateDefault(arg)); + return Expression.Condition(condition, getter, getter.Type.CreateDefault()); } if (expr.CanBeNull()) From 85e558f7782de9ccb618ce6119b1894f7cf3146b Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 6 Aug 2026 15:58:05 +0500 Subject: [PATCH 14/14] feat: update null propagation --- .../WhenMapUsingOverrideTypesSettings.cs | 80 +++++++++++++ src/Mapster/Adapters/BaseClassAdapter.cs | 6 +- src/Mapster/Adapters/ClassAdapter.cs | 4 +- .../Utils/DirectParameterMemberFinder.cs | 112 +++++++++++++++++ src/Mapster/Utils/ExpressionEx.cs | 113 +++++++++++++++++- src/Mapster/Utils/StringComparer.cs | 24 ++++ 6 files changed, 331 insertions(+), 8 deletions(-) create mode 100644 src/Mapster/Utils/DirectParameterMemberFinder.cs create mode 100644 src/Mapster/Utils/StringComparer.cs diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index e175adb1..ddacef3a 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -2,6 +2,7 @@ using Shouldly; using System; using System.Collections.Generic; +using System.Linq; namespace Mapster.Tests { @@ -163,8 +164,87 @@ public void ReMapSettersIsWorked() reMapResut.Value.ShouldBe(default); } + [TestMethod] + public void ApplyPropagantionUsingDeepSrcAnalize() + { + var config = new TypeAdapterConfig(); + + config.NewConfig() + .Map(dest => dest.ProductNames, src => src.Products.Select(x => x.Name).ToArray()); + + config.NewConfig() + .Map(dest => dest.ProductNames, src => src.Products.Select(x => x.Name).ToArray()); + + config.NewConfig() + .Map(dest => dest.Result, src => $"{src.Value1.ToString()}"); + + config.NewConfig() + .Map(dest => dest.Result, src => $"{src.Value1.ToString()}"); + + var src = new Source1004(); + var srcStrings = new NullableStrings(); + + //var str = src.BuildAdapter(config).CreateMapExpression(); + //var str2 = src.BuildAdapter(config).CreateMapExpression(); + + Should.NotThrow(() => + { + src.Adapt(config); + src.Adapt(config); + + srcStrings.Adapt(config); + srcStrings.Adapt(config); + }); + } + #region TestClasses + class Source1004 + { + public Product1004[]? Products { get; set; } + } + + class Product1004 + { + public required string Name { get; set; } + } + + class Destination1004 + { + public string[]? ProductNames { get; set; } + } + + class DestinationCtor1004 + { + public DestinationCtor1004(string[]? productNames) + { + ProductNames = productNames; + } + + public string[]? ProductNames { get; } + } + + public class NullableStrings + { + public string Value1 { get; set; } + + public string Value2 { get; set; } + } + + public class NullableStringsDest + { + public string Result { get; set; } + } + public class NullableStringsDestCtor + { + public NullableStringsDestCtor(string result) + { + Result = result; + } + + public string Result { get;} + } + public class RemapMemberMappings { public int Data { get; set; } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index a1888d38..418270e2 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -137,9 +137,9 @@ select fn(src, destinationMember, arg)) } if (getter != null) { - propertyModel.Getter = arg.MapType == MapType.Projection - ? getter - : getter.ApplyPropertyNullPropagation(arg); + propertyModel.Getter = arg.MapType == MapType.Projection || ctorMapping + ? getter + : getter.ApplyPropertyNullPropagation(arg, source); properties.Add(propertyModel); } else diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 2184db42..c7e9d253 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -253,8 +253,8 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre if (member.UseDestinationValue) return null; - if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) - && member.Getter is MemberExpression memberExp && contructorMembers.Contains(memberExp.Member)) + if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) + && contructorMembers.Select(x => x.Name).Contains(member.DestinationMember.Name, new MapsterStringComparer())) continue; if (member.DestinationMember.SetterModifier == AccessModifier.None) diff --git a/src/Mapster/Utils/DirectParameterMemberFinder.cs b/src/Mapster/Utils/DirectParameterMemberFinder.cs new file mode 100644 index 00000000..495055fa --- /dev/null +++ b/src/Mapster/Utils/DirectParameterMemberFinder.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; + +public class DirectParameterMemberFinder : ExpressionVisitor +{ + private readonly bool _isCtrMapping; + private readonly HashSet _TargetParams; + public List FoundMembers { get; } = new(); + + public DirectParameterMemberFinder(bool conctructorMapping = false, params Expression[] targetParams) + { + _TargetParams = new HashSet(targetParams); + _isCtrMapping = conctructorMapping; + } + + protected override Expression VisitMember(MemberExpression node) + { + if (_TargetParams.Contains(GetParametr(node))) + if (_isCtrMapping) + FoundMembers.Add(node); + else + FoundMembers.Add(node.Expression); + + return node; + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Object is MemberExpression mem && _TargetParams.Contains(GetParametr(mem))) + FoundMembers.Add(mem); + + foreach (var arg in node.Arguments) + { + if (arg is MemberExpression member && _TargetParams.Contains(GetParametr(member))) + { + // if Method is static for Type && not Extention method + if (node.Object == null && !node.Method.IsDefined(typeof(ExtensionAttribute), inherit: false)) + FoundMembers.Add(member.Expression); + else + FoundMembers.Add(member); + continue; + } + + if (arg.NodeType == ExpressionType.Call) + { + Visit(arg); + } + } + + return node; + } + + protected override Expression VisitUnary(UnaryExpression node) + { + if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked) + { + var result = base.VisitUnary(node); + return result; + } + return base.VisitUnary(node); + } + + public IEnumerable Find(Expression expression) + { + FoundMembers.Clear(); + Visit(expression); + + return FoundMembers; + } + + + private Expression GetParametr(MemberExpression member) + { + Expression current = member; + + while (current != null) + { + if (current is MemberExpression mem) + { + current = mem.Expression; + continue; + } + + if (current is ParameterExpression) + return current; + else + current = new ReturnParametrVisitor().GetParam(current); + } + + return Expression.Empty(); + } + + internal class ReturnParametrVisitor : ExpressionVisitor + { + private Expression parametr; + + protected override Expression VisitParameter(ParameterExpression node) + { + parametr = node; + return node; + } + + public Expression GetParam(Expression expression) + { + Visit(expression); + return parametr; + } + } + + +} \ No newline at end of file diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 0742852c..6a768755 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -421,7 +421,114 @@ public static Expression NullableEnumExtractor(this Expression param) return param; } - public static Expression ApplyPropertyNullPropagation(this Expression getter, CompileArgument arg) + + public static Expression ApplyPropertyNullPropagation(this Expression getter, CompileArgument arg, Expression source) + { + var current = getter; + var result = getter; + Expression? condition = null; + + var finder = new DirectParameterMemberFinder(false,source); + var condition2 = finder.Find(getter) + .Select(x => x.GetNullPropagationChecks(arg)) + .Where(x => x != null) + .ToArray().ConcatPropagationChecks(); + + if (condition2 == null) + return getter; + + if (!getter.Type.CanBeNull()) + { + var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type)); + return Expression.Condition(condition2, transform, transform.Type.CreateDefault()); + } + else + return Expression.Condition(condition2, getter, getter.Type.CreateDefault()); + } + + public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg, MemberMapping mapping) + { + if (getter == null) + return adapt; + + var finder = new DirectParameterMemberFinder(true,mapping.Source); + + Expression? condition = finder.Find(getter) + .Select(x => x.GetNullPropagationChecks(arg)) + .Where(x => x != null) + .ToArray().ConcatPropagationChecks(); + + if (condition == null) + return adapt; + + // add supporting DestinationTransforms + var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(adapt.Type)); + if (transform != null) + return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg))); + + return Expression.Condition(condition, adapt, adapt.Type.CreateDefault(member: mapping)); + } + + + private static Expression? ConcatPropagationChecks(this Expression[] checks) + { + if (checks.Length == 0) + return null; + + if (checks.Length == 1) + return checks.First(); + + Expression? result = null; + + for (int i = 0; i < checks.Length; i++) + { + if (i == 0) + result = checks[i]; + else + { + result = Expression.AndAlso(result, checks[i]); + } + + } + + return result; + } + + private static Expression? GetNullPropagationChecks (this Expression getter, CompileArgument arg) + { + Expression? condition = null; + var current = getter; + + while (current != null) + { + Expression? compareNull = null; + + if (current.Type.CanBeNull() && current is not ParameterExpression) + compareNull = Expression.NotEqual(current, Expression.Constant(null, current.Type)); + + else if (current.Type.CanBeNull() && current is ParameterExpression param + && arg.MapType == MapType.Projection) + + compareNull = Expression.NotEqual(param, Expression.Constant(null, param.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; + } + + return condition; + } + + public static Expression ApplyPropertyNullPropagationLegasy(this Expression getter, CompileArgument arg) { var current = getter; var result = getter; @@ -429,7 +536,7 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter, Co while (current.NodeType == ExpressionType.MemberAccess) { - var memEx = (MemberExpression) current; + var memEx = (MemberExpression)current; var expr = memEx.Expression; if (expr == null) break; @@ -459,7 +566,7 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter, Co return getter; } - public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg, MemberMapping mapping) + public static Expression ApplyNullPropagationFromCtorLegasy(this Expression getter, Expression adapt, CompileArgument arg, MemberMapping mapping) { if (getter == null) return adapt; diff --git a/src/Mapster/Utils/StringComparer.cs b/src/Mapster/Utils/StringComparer.cs new file mode 100644 index 00000000..e3f0d7f4 --- /dev/null +++ b/src/Mapster/Utils/StringComparer.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Mapster.Utils +{ + internal class MapsterStringComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) + { + if(String.IsNullOrEmpty(x) || String.IsNullOrEmpty(y)) + return false; + + return String.Equals(x, y, StringComparison.InvariantCultureIgnoreCase); + } + + public int GetHashCode(string obj) + { + if(obj is null) + return 0; + + return obj.GetHashCode(); + } + } +}