From 0caa9b71fcff0bb324a4529f8b4cefddcb411f80 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 22 Jul 2026 06:55:10 +0300 Subject: [PATCH 01/31] feat: add MapUsing override Type mapping setting (#985) * feat: start map Override * feat: impliment Map with TypeSettings ovveride * feat(test): add test from MapWithOverrideTypesSettings feature * fix: rename to MapUsing & add SkipAllSettings settings * Fix: SkipAll --- .../WhenMapUsingOverrideTypesSettings.cs | 89 +++++++++++++++++++ src/Mapster/Adapters/BaseAdapter.cs | 4 +- src/Mapster/Adapters/BaseClassAdapter.cs | 13 +++ src/Mapster/Models/InvokerModel.cs | 1 + src/Mapster/Models/MemberMapping.cs | 1 + src/Mapster/Settings/SettingStore.cs | 38 ++++++-- src/Mapster/TypeAdapterConfig.cs | 46 ++++++++++ src/Mapster/TypeAdapterSetter.cs | 46 ++++++++++ .../TypeAdapterSetters/OverrideTypesSetter.cs | 41 +++++++++ .../OverrideTypesSettings.cs | 31 +++++++ src/Mapster/Utils/ExpressionEx.cs | 2 +- 11 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs create mode 100644 src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs create mode 100644 src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs new file mode 100644 index 00000000..e7142260 --- /dev/null +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -0,0 +1,89 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using System; +using System.Collections.Generic; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenMapUsingOverrideTypesSettings + { + [TestMethod] + public void OverrideDestinationTramsformIsWorked() + { + var config = new TypeAdapterConfig(); + config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + + config + .NewConfig() + .MapUsing(src => src.Children, dest => dest.Children, + cfg => + { + cfg.SkipAllSettings(true); + }) + .MapUsing(src => src.Array, dest => dest.Array, + cfg => + { + cfg + .ReConfigurate() + .MapWith(x => x ?? new[] { 42 }); + }); + + + var source = new CollectionPocoOverride(); + var destination = source.Adapt(config); + + destination.MultiDimentionalArray.Length.ShouldBe(0); + destination.ChildDict.Count.ShouldBe(0); + destination.Set.Count.ShouldBe(0); + + + destination.Children.ShouldBeNull(); // Destination Transforms from global context settings is skipped for this property + destination.Array[0].ShouldBe(42); // Custom converter for types is worked, Destination Transforms is not achievable because the custom converter never returns null + + + var destWithNotTypesSettingOverride = new CollectionPocoOverride().Adapt(config); + + // Destination Transforms correct work from other mapping types + destWithNotTypesSettingOverride.Array.Length.ShouldBe(0); + } + + + #region TestClasses + + class CollectionPocoWithArray + { + public int[] Array { get; set; } + } + + class CollectionDtoWithArray + { + public int[] Array { get; set; } + } + + class CollectionPocoOverride + { + public Guid Id { get; set; } + public string Name { get; set; } + + public List Children { get; set; } + public int[] Array { get; set; } + public double[,] MultiDimentionalArray { get; set; } + public Dictionary ChildDict { get; set; } + public HashSet Set { get; set; } + } + + class CollectionDtoOverride + { + public Guid Id { get; set; } + public string Name { get; set; } + + public IReadOnlyList Children { get; internal set; } + public int[] Array { get; set; } + public double[,] MultiDimentionalArray { get; set; } + public IReadOnlyDictionary ChildDict { get; set; } + public ISet Set { get; set; } + } + #endregion TestClasses + } +} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 58e95e86..48d8ba18 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -512,7 +512,9 @@ internal Expression CreateAdaptExpression(Expression source, Type destinationTyp //transform(adapt(_source)); if (notUsingDestinationValue) { - var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(exp.Type)); + var settings = mapping?.OverrideSettings ?? arg.Settings; + + var transform = settings.DestinationTransforms.Find(it => it.Condition(exp.Type)); if (transform != null) exp = transform.TransformFunc(exp.Type).Apply(arg.MapType, exp); } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 140f8b18..755d7a0d 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -43,6 +43,12 @@ src is LambdaExpression lambda from src in sources select fn(src, destinationMember, arg)) .FirstOrDefault(result => result != null); + 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) { @@ -103,6 +109,10 @@ select fn(src, destinationMember, arg)) 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, @@ -112,6 +122,7 @@ select fn(src, destinationMember, arg)) Source = (ParameterExpression)source, Destination = (ParameterExpression?)destination, UseDestinationValue = IsCanUsingDestinationValue(arg, destinationMember), + OverrideSettings = overideSettings }; if(arg.MapType == MapType.ApplyNullPropagation && getter == null && !arg.DestinationType.IsRecordType() @@ -265,6 +276,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi else getter = member.Getter .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member), arg); + if (member.Ignore.Condition != null) @@ -283,6 +295,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination) ?? getter; } + } arguments.Add(getter); } diff --git a/src/Mapster/Models/InvokerModel.cs b/src/Mapster/Models/InvokerModel.cs index b5a4546d..f2201c3d 100644 --- a/src/Mapster/Models/InvokerModel.cs +++ b/src/Mapster/Models/InvokerModel.cs @@ -9,6 +9,7 @@ public class InvokerModel public LambdaExpression? Invoker { get; set; } public string? SourceMemberName { get; set; } public LambdaExpression? Condition { get; set; } + public TypeAdapterSettings? OvverideSettings { get; set; } public bool IsChildPath { get; set; } public InvokerModel? Next(ParameterExpression source, string destMemberName) diff --git a/src/Mapster/Models/MemberMapping.cs b/src/Mapster/Models/MemberMapping.cs index d047e4ba..008277d6 100644 --- a/src/Mapster/Models/MemberMapping.cs +++ b/src/Mapster/Models/MemberMapping.cs @@ -13,6 +13,7 @@ internal class MemberMapping public ParameterExpression Source; public ParameterExpression? Destination; public bool UseDestinationValue; + public TypeAdapterSettings? OverrideSettings; public bool HasSettings() { diff --git a/src/Mapster/Settings/SettingStore.cs b/src/Mapster/Settings/SettingStore.cs index 821b8569..fbf34b9b 100644 --- a/src/Mapster/Settings/SettingStore.cs +++ b/src/Mapster/Settings/SettingStore.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; namespace Mapster { @@ -46,19 +48,18 @@ public T Get(string key, Func initializer) where T : class return (T)_objectStore.GetOrAdd(key, _ => initializer()); } - public virtual void Apply(object other) - { - if (other is SettingStore settingStore) - Apply(settingStore); - } - public void Apply(SettingStore other) + + private void ApplyBoolSettings (IEnumerable> otherBoolStore) { - foreach (var kvp in other._booleanStore) + foreach (var kvp in otherBoolStore) { _booleanStore.TryAdd(kvp.Key, kvp.Value); } + } - foreach (var kvp in other._objectStore) + private void ApplyObjectSettings(IEnumerable> otherBoolStore) + { + foreach (var kvp in otherBoolStore) { var self = _objectStore.GetOrAdd(kvp.Key, key => { @@ -80,5 +81,26 @@ public void Apply(SettingStore other) } } } + + + public virtual void Apply(object other) + { + if (other is SettingStore settingStore) + Apply(settingStore); + } + + + public virtual void Apply(SettingStore other) + { + ApplyBoolSettings(other._booleanStore); + ApplyObjectSettings(other._objectStore); + } + + public virtual void ApplyWithSkipSettings(SettingStore other, List skipSettingNames) + { + ApplyBoolSettings(other._booleanStore.Where(x => !skipSettingNames.Contains(x.Key))); + ApplyObjectSettings(other._objectStore.Where(x => !skipSettingNames.Contains(x.Key))); + } + } } \ No newline at end of file diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 08db194a..4ebbe511 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -544,6 +544,26 @@ internal LambdaExpression CreateInlineMapExpression(Type sourceType, Type destin arg.Settings.Resolvers.AddRange(mapping.NextResolvers); arg.Settings.Ignore.Apply(mapping.NextIgnore); arg.UseDestinationValue = mapping.UseDestinationValue; + + if (mapping.OverrideSettings != null) + { + mapping.OverrideSettings.Apply(arg.Settings); + + if(mapping.OverrideSettings.ConverterFactory == null || mapping.OverrideSettings.ConverterToTargetFactory == null) + { + var defaultfactory = GetOvverideDefaultSettings(tuple, mapType); + + if (mapping.OverrideSettings.ConverterFactory == null) + mapping.OverrideSettings.ConverterFactory = defaultfactory.ConverterFactory; + if (mapping.OverrideSettings.ConverterToTargetFactory == null) + mapping.OverrideSettings.ConverterToTargetFactory = defaultfactory.ConverterToTargetFactory; + + } + + + arg.Settings = mapping.OverrideSettings; + } + } return CreateMapExpression(arg); @@ -697,6 +717,32 @@ orderby priority.Value descending return result; } + internal TypeAdapterSettings GetOvverideDefaultSettings(TypeTuple tuple, MapType mapType) + { + var arg = new PreCompileArgument + { + SourceType = tuple.Source, + DestinationType = tuple.Destination, + MapType = mapType, + ExplicitMapping = true, + }; + + var result = new TypeAdapterSettings(); + + var rules = RulesTemplate.Reverse(); + var settings = from rule in rules + let priority = rule.Priority(arg) + where priority != null + orderby priority.Value descending + select rule.Settings; + foreach (var setting in settings) + { + result.Apply(setting); + } + + return result; + } + private CompileArgument GetCompileArgument(TypeTuple tuple, MapType mapType, CompileContext context) { var setting = GetMergedSettings(tuple, mapType); diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 7a78af01..2e351574 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -22,6 +22,16 @@ public TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig config) Settings = settings; Config = config; } + + internal static TypeAdapterSetter CreateMapTypeOverride() + { + return new TypeAdapterSetter(new TypeAdapterSettings(), null); + } + + internal static TypeAdapterSetter CreateMapTypeOverride() + { + return new TypeAdapterSetter(new TypeAdapterSettings(), null); + } } public static class TypeAdapterSetterExtensions { @@ -628,6 +638,42 @@ public TypeAdapterSetter IgnoredRemove(params Expression MapUsing( + Expression> member, + Expression> source, + Action>? configAction = null) + { + this.CheckCompiled(); + + var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof(object))); + if (member.IsIdentity()) + { + Settings.ExtraSources.Add(invoker); + return this; + } + + TypeAdapterSettings? overrideSettings = null; + + if (configAction != null) + { + var Tempsetter = new OverrideTypesSetter(); + configAction(Tempsetter); + + overrideSettings = Tempsetter.Settings; + } + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = member.GetMemberPath()!, + Invoker = invoker, + Condition = null, + OvverideSettings = overrideSettings + }); + return this; + } + + public TypeAdapterSetter IgnoreIf( Expression> condition, diff --git a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs new file mode 100644 index 00000000..ec2c8f6f --- /dev/null +++ b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs @@ -0,0 +1,41 @@ +using Mapster.Utils; +using System; +using System.Linq.Expressions; + +namespace Mapster +{ + [AdaptWith(AdaptDirectives.DestinationAsRecord)] + public class OverrideTypesSetter : TypeAdapterSetter + { + protected OverrideTypesSettings _Settings { get => (OverrideTypesSettings)Settings; } + + public OverrideTypesSetter() : this (new OverrideTypesSettings (), null) { } + public OverrideTypesSetter(TypeAdapterSettings settings, TypeAdapterConfig config) : base(settings, config) { } + } + + public class OverrideTypesSetter : OverrideTypesSetter + { + public OverrideTypesSetter SkipAllSettings(bool value) + { + _Settings.SkipAllSettings = value; + return this; + } + + public OverrideTypesSetter SkipSettings(params Expression>[] settings) + { + foreach (var member in settings) + { + _Settings.SkipSettings.Add(member.GetMemberPath()!); + } + + return this; + } + + public TypeAdapterSetter ReConfigurate() + { + return new TypeAdapterSetter(this.Settings,this.Config); + } + } + + +} diff --git a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs new file mode 100644 index 00000000..ebb160c4 --- /dev/null +++ b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Mapster +{ + [AdaptWith(AdaptDirectives.DestinationAsRecord)] + public class OverrideTypesSettings : TypeAdapterSettings + { + public List SkipSettings + { + get => Get(nameof(SkipSettings), () => new List()); + } + + public bool? SkipAllSettings + { + get => Get(nameof(SkipAllSettings)); + set => Set(nameof(SkipAllSettings), value); + } + + public override void Apply(object other) + { + if (other is SettingStore settingStore) + Apply(settingStore); + } + + public override void Apply(SettingStore other) + { + if(!SkipAllSettings.GetValueOrDefault()) + base.ApplyWithSkipSettings(other, SkipSettings); + } + } +} diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index c55d96c5..33d13cb7 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -150,7 +150,7 @@ public static Expression Not(Expression exp) public static Expression Apply(this LambdaExpression lambda, MapType mapType, params Expression[] exps) { return lambda.Apply(mapType != MapType.Projection, exps); - } + } public static Expression Apply(this LambdaExpression lambda, ParameterExpression p1, ParameterExpression? p2 = null) { From 272d395f821c7cc23d20aaa165fa68a1dbaf699c Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 23 Jul 2026 07:48:20 +0300 Subject: [PATCH 02/31] feat: add Custom Default Value for destination Type (#1001) * fix: TypeOvveride setter * fix: Drop not using init * feat: add test from CustomDefaultValue * feat: add CustomDefaulValue impl * feat: mod test --- .../WhenMapUsingOverrideTypesSettings.cs | 42 +++++++++++++++++++ src/Mapster/Adapters/BaseAdapter.cs | 8 ++-- src/Mapster/Adapters/BaseClassAdapter.cs | 22 +++++----- src/Mapster/Adapters/ClassAdapter.cs | 2 +- src/Mapster/Adapters/DictionaryAdapter.cs | 2 +- src/Mapster/Adapters/NullableAdapter.cs | 3 ++ src/Mapster/Adapters/PrimitiveAdapter.cs | 4 +- src/Mapster/Adapters/RecordTypeAdapter.cs | 4 +- .../Settings/ValueAccessingStrategy.cs | 4 +- src/Mapster/TypeAdapter.cs | 8 +++- src/Mapster/TypeAdapterConfig.cs | 6 ++- src/Mapster/TypeAdapterSetter.cs | 21 +++++----- .../TypeAdapterSetters/OverrideTypesSetter.cs | 10 ++++- src/Mapster/TypeAdapterSettings.cs | 6 +++ src/Mapster/Utils/ExpressionEx.cs | 12 +++--- src/Mapster/Utils/ReflectionUtils.cs | 5 ++- 16 files changed, 114 insertions(+), 45 deletions(-) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index e7142260..8ee49827 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -48,9 +48,51 @@ public void OverrideDestinationTramsformIsWorked() destWithNotTypesSettingOverride.Array.Length.ShouldBe(0); } + [TestMethod] + public void UsingDefaultValueIsWorked() + { + var config = new TypeAdapterConfig(); + + config.ForDestinationType() + .DefaultValue(x => 32); + + config.ForDestinationType() + .DefaultValue(x=>42); + + int? src = null; + var srcInsaider = new NullableIntInsaider() { Data = null }; + + + var resultCD = src.Adapt(config); + var resultCDInsaider = srcInsaider.Adapt(config); + + resultCD.ShouldBe(32); + resultCDInsaider.Data.ShouldBe(42); + + config. + NewConfig() + .MapUsing(dest => dest.Data, src => src.Data, cfg => + { + cfg.ReConfigurate() + .DefaultValue(x => 35); + }); + + var resultCDInsaiderReconfig = srcInsaider.Adapt(config); + + resultCDInsaiderReconfig.Data.ShouldBe(35); + } #region TestClasses + public class NullableIntInsaider + { + public int? Data { get; set; } + } + + public class NullableIntInsaiderReconfig + { + public int? Data { get; set; } + } class CollectionPocoWithArray { public int[] Array { get; set; } diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 48d8ba18..f5cafc09 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -97,7 +97,7 @@ protected virtual Expression CreateExpressionBody(Expression source, Expression? if (arg.Context.MaxDepth.HasValue) { if (ObjectType != ObjectType.Primitive && arg.Context.Depth >= arg.Context.MaxDepth.Value) - return arg.DestinationType.CreateDefault(); + return arg.DestinationType.CreateDefault(arg); if (ObjectType == ObjectType.Class) arg.Context.Depth++; } @@ -259,7 +259,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de var compareNull = Expression.Equal(source, Expression.Constant(null, source.Type)); blocks.Add( Expression.IfThen(compareNull, - Expression.Return(label, arg.DestinationType.CreateDefault())) + Expression.Return(label, arg.DestinationType.CreateDefault(arg))) ); } @@ -351,7 +351,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de } } - blocks.Add(Expression.Label(label, arg.DestinationType.CreateDefault())); + blocks.Add(Expression.Label(label, arg.DestinationType.CreateDefault(arg))); return Expression.Block(vars, blocks); } @@ -390,7 +390,7 @@ private static Expression InvokeMapping( //projection null is handled by EF if (arg.MapType != MapType.Projection) - exp = source.NotNullReturn(exp); + exp = source.NotNullReturn(exp,arg); return exp; } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 755d7a0d..7bcadabe 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -131,19 +131,19 @@ select fn(src, destinationMember, arg)) if (propinfo.GetCustomAttributes() .Any(y => y.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute")) { - getter = destinationMember.Type.CreateDefault(); + getter = destinationMember.Type.CreateDefault(arg); } } if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType()) { - getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination) ?? getter; + getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination, arg) ?? getter; } if (getter != null) { propertyModel.Getter = arg.MapType == MapType.Projection ? getter - : getter.ApplyPropertyNullPropagation(); + : getter.ApplyPropertyNullPropagation(arg); properties.Add(propertyModel); } else @@ -241,17 +241,17 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi { defaultConst = parameterInfo.IsOptional && parameterInfo.DefaultValue != null ? Expression.Constant(parameterInfo.DefaultValue, member.DestinationMember.Type) - : parameterInfo.ParameterType.CreateDefault(); + : parameterInfo.ParameterType.CreateDefault(arg); } catch (FormatException) { - defaultConst = parameterInfo.ParameterType.CreateDefault(); + defaultConst = parameterInfo.ParameterType.CreateDefault(arg); } #else defaultConst = parameterInfo.IsOptional && parameterInfo.DefaultValue != null ? Expression.Constant(parameterInfo.DefaultValue, member.DestinationMember.Type) - : parameterInfo.ParameterType.CreateDefault(); + : parameterInfo.ParameterType.CreateDefault(arg); #endif if (member.Getter == null) @@ -259,7 +259,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) - getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination) ?? getter; + getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination, arg) ?? getter; } else { @@ -283,7 +283,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi { var body = member.Ignore.IsChildPath ? member.Ignore.Condition.Body - : member.Ignore.Condition.Apply(arg.MapType, source, arg.DestinationType.CreateDefault()); + : member.Ignore.Condition.Apply(arg.MapType, source, arg.DestinationType.CreateDefault(arg)); var condition = ExpressionEx.Not(body); getter = Expression.Condition(condition, getter, defaultConst); } @@ -293,7 +293,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) - getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination) ?? getter; + getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination, arg) ?? getter; } } @@ -348,7 +348,7 @@ protected virtual ClassModel GetOnlyRequiredPropertySetterModel(CompileArgument }; } - protected Expression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination) + protected Expression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination, CompileArgument arg) { if (restorRecordModel != null && destination != null) { @@ -358,7 +358,7 @@ protected virtual ClassModel GetOnlyRequiredPropertySetterModel(CompileArgument if (find != null) { var compareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type)); - return Expression.Condition(compareNull, member.Type.CreateDefault(), Expression.MakeMemberAccess(destination, (MemberInfo)find.Info)); + return Expression.Condition(compareNull, member.Type.CreateDefault(arg), Expression.MakeMemberAccess(destination, (MemberInfo)find.Info)); } } diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index d71067b0..0f2c4bc5 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -271,7 +271,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre && !member.DestinationMember.Type.IsCollection() && member.Getter.Type.GetTypeInfo().GetCustomAttributesData().All(attr => attr.GetAttributeType().Name != "ComplexTypeAttribute")) { - value = member.Getter.NotNullReturn(value); + value = member.Getter.NotNullReturn(value,arg); } var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, value); lines.Add(bind); diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index 1f5ef77e..cc2ad2db 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -159,7 +159,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio actions.Add(loop); if (label != null) - actions.Add(Expression.Label(label, arg.DestinationType.CreateDefault())); + actions.Add(Expression.Label(label, arg.DestinationType.CreateDefault(arg))); return shouldConvert ? Expression.Block(new[] {(ParameterExpression)dict}, actions) diff --git a/src/Mapster/Adapters/NullableAdapter.cs b/src/Mapster/Adapters/NullableAdapter.cs index 178c3266..ca83790e 100644 --- a/src/Mapster/Adapters/NullableAdapter.cs +++ b/src/Mapster/Adapters/NullableAdapter.cs @@ -26,6 +26,9 @@ protected override bool CanInline(Expression source, Expression? destination, Co ? Expression.Convert(source, source.Type.GetGenericArguments()[0]) : source; + //var destType = arg.DestinationType.GetNotNullableTypeDefenition(); + //var customArg = arg.Context.Config.GetCompileArgument(_source.Type, destType, arg.MapType, arg.Context); + Expression adapt = CreateAdaptExpression(_source, arg.DestinationType.GetNotNullableTypeDefenition(),arg); return adapt.ToNullableExp(arg); diff --git a/src/Mapster/Adapters/PrimitiveAdapter.cs b/src/Mapster/Adapters/PrimitiveAdapter.cs index 622f32b1..ead180d1 100644 --- a/src/Mapster/Adapters/PrimitiveAdapter.cs +++ b/src/Mapster/Adapters/PrimitiveAdapter.cs @@ -27,7 +27,7 @@ protected override Expression CreateExpressionBody(Expression source, Expression if (destination == null) { - dest = arg.DestinationType.CreateDefault(); + dest = arg.DestinationType.CreateDefault(arg); } else dest = destination; @@ -56,7 +56,7 @@ protected override Expression CreateExpressionBody(Expression source, Expression { //src == null ? default(TDestination) : convert(src) var compareNull = Expression.Equal(source, Expression.Constant(null, sourceType)); - convert = Expression.Condition(compareNull, destinationType.CreateDefault(), convert); + convert = Expression.Condition(compareNull, destinationType.CreateDefault(arg), convert); } } diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 63c32747..9a5bdb31 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -98,7 +98,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E } var destinationCompareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type)); var sourceCondition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); - var destinationCanbeNull = Expression.Condition(destinationCompareNull, member.DestinationMember.Type.CreateDefault(), member.DestinationMember.GetExpression(destination)); + var destinationCanbeNull = Expression.Condition(destinationCompareNull, member.DestinationMember.Type.CreateDefault(arg), member.DestinationMember.GetExpression(destination)); adapt = Expression.Condition(sourceCondition, adapt, destinationCanbeNull); } } @@ -112,7 +112,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E && !member.DestinationMember.Type.IsCollection() && member.Getter.Type.GetTypeInfo().GetCustomAttributesData().All(attr => attr.GetAttributeType().Name != "ComplexTypeAttribute")) { - adapt = member.Getter.NotNullReturn(adapt); + adapt = member.Getter.NotNullReturn(adapt,arg); } var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, adapt); lines.Add(bind); diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index 4fb608dc..a94c7d79 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -58,7 +58,7 @@ public static class ValueAccessingStrategy var type = invokes[0].Item2.Type; if (destinationMember.Type.CanBeNull() && !type.CanBeNull()) type = typeof(Nullable<>).MakeGenericType(type); - getter = type.CreateDefault(); + getter = type.CreateDefault(arg); } foreach (var invoke in invokes) { @@ -235,7 +235,7 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM break; } if (lastCondition != null) - getter = Expression.Condition(lastCondition, getter!, getter!.Type.CreateDefault()); + getter = Expression.Condition(lastCondition, getter!, getter!.Type.CreateDefault(arg)); return getter; } } diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index b05c1ba7..18e25194 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -43,9 +43,13 @@ public static ITypeAdapterBuilder BuildAdapter(this TSource so public static TDestination? Adapt(this object? source, TypeAdapterConfig config) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse + Type? type; + if (source == null) - return default; - var type = source.GetType(); + type = typeof(Object); + else + type = source.GetType(); + var fn = config.GetDynamicMapFunction(type); return fn(source)!; } diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 4ebbe511..8758e064 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -450,7 +450,7 @@ private static LambdaExpression AdjustInheritedConverterReturnType(LambdaExpress { if(arg.Settings.ApplyCustomConverterFactoryNullPropagation.GetValueOrDefault()) - lambda = Expression.Lambda(lambda.Parameters[0].NotNullReturn(lambda.Body),lambda.Parameters); + lambda = Expression.Lambda(lambda.Parameters[0].NotNullReturn(lambda.Body,arg),lambda.Parameters); var destinationType = arg.DestinationType; var returnType = lambda.ReturnType; @@ -743,6 +743,10 @@ orderby priority.Value descending return result; } + internal CompileArgument GetCompileArgument(Type sourcetype, Type destintaiontype, MapType mapType, CompileContext context) + { + return GetCompileArgument(new TypeTuple(sourcetype, destintaiontype), mapType, context); + } private CompileArgument GetCompileArgument(TypeTuple tuple, MapType mapType, CompileContext context) { var setting = GetMergedSettings(tuple, mapType); diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 2e351574..2577cde5 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -22,16 +22,6 @@ public TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig config) Settings = settings; Config = config; } - - internal static TypeAdapterSetter CreateMapTypeOverride() - { - return new TypeAdapterSetter(new TypeAdapterSettings(), null); - } - - internal static TypeAdapterSetter CreateMapTypeOverride() - { - return new TypeAdapterSetter(new TypeAdapterSettings(), null); - } } public static class TypeAdapterSetterExtensions { @@ -423,6 +413,15 @@ public TypeAdapterSetter Ignore(params Expression DefaultValue(Expression> defaultValue) + { + this.CheckCompiled(); + + Settings.CustomDefaultValue = defaultValue.Body; + + return this; + } + public TypeAdapterSetter Map( Expression> member, Expression> source) @@ -657,7 +656,7 @@ public TypeAdapterSetter MapUsing(); + var Tempsetter = new OverrideTypesSetter(this.Config); configAction(Tempsetter); overrideSettings = Tempsetter.Settings; diff --git a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs index ec2c8f6f..79ec8011 100644 --- a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs +++ b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs @@ -9,12 +9,20 @@ public class OverrideTypesSetter : TypeAdapterSetter { protected OverrideTypesSettings _Settings { get => (OverrideTypesSettings)Settings; } - public OverrideTypesSetter() : this (new OverrideTypesSettings (), null) { } + public OverrideTypesSetter(TypeAdapterConfig config) : this (new OverrideTypesSettings (), config) { } public OverrideTypesSetter(TypeAdapterSettings settings, TypeAdapterConfig config) : base(settings, config) { } } public class OverrideTypesSetter : OverrideTypesSetter { + public OverrideTypesSetter(TypeAdapterConfig config) : base(config) + { + } + + public OverrideTypesSetter(TypeAdapterSettings settings, TypeAdapterConfig config) : base(settings, config) + { + } + public OverrideTypesSetter SkipAllSettings(bool value) { _Settings.SkipAllSettings = value; diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs index 65f8b6e4..f72a25a6 100644 --- a/src/Mapster/TypeAdapterSettings.cs +++ b/src/Mapster/TypeAdapterSettings.cs @@ -202,6 +202,12 @@ public List UseDestinationMembers get => Get(nameof(UseDestinationMembers), () => new List()); } + public Expression? CustomDefaultValue + { + get => Get(nameof(CustomDefaultValue)); + set => Set(nameof(CustomDefaultValue), value); + } + internal bool Compiled { get; set; } public TypeAdapterSettings Clone() diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 33d13cb7..4b9b0e18 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -150,7 +150,7 @@ public static Expression Not(Expression exp) public static Expression Apply(this LambdaExpression lambda, MapType mapType, params Expression[] exps) { return lambda.Apply(mapType != MapType.Projection, exps); - } + } public static Expression Apply(this LambdaExpression lambda, ParameterExpression p1, ParameterExpression? p2 = null) { @@ -371,7 +371,7 @@ public static bool IsMultiLine(this LambdaExpression lambda) return detector.IsBlockExpression; } - public static Expression NotNullReturn(this Expression exp, Expression value) + public static Expression NotNullReturn(this Expression exp, Expression value, CompileArgument arg) { if (value.IsSingleValue() || !exp.CanBeNull()) return value; @@ -379,7 +379,7 @@ public static Expression NotNullReturn(this Expression exp, Expression value) var compareNull = Expression.Equal(exp, Expression.Constant(null, exp.Type)); return Expression.Condition( compareNull, - value.Type.CreateDefault(), + value.Type.CreateDefault(arg), value); } @@ -407,7 +407,7 @@ public static Expression NullableEnumExtractor(this Expression param) return param; } - public static Expression ApplyPropertyNullPropagation(this Expression getter) + public static Expression ApplyPropertyNullPropagation(this Expression getter, CompileArgument arg) { var current = getter; var result = getter; @@ -424,10 +424,10 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter) if (!getter.CanBeNull()) { var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type)); - return Expression.Condition(condition, transform, transform.Type.CreateDefault()); + return Expression.Condition(condition, transform, transform.Type.CreateDefault(arg)); } else - return Expression.Condition(condition, getter, getter.Type.CreateDefault()); + return Expression.Condition(condition, getter, getter.Type.CreateDefault(arg)); } if (expr.CanBeNull()) diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 8203858f..d5051c43 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -358,8 +358,11 @@ public static bool IsPrimitiveKind(this Type type) return type == typeof(object) || type.UnwrapNullable().IsConvertible(); } - public static Expression CreateDefault(this Type type) + public static Expression CreateDefault(this Type type, CompileArgument? arg = null) { + if(arg !=null && arg.Settings.CustomDefaultValue != null) + return arg.Settings.CustomDefaultValue; + return type.CanBeNull() ? Expression.Constant(null, type) : Expression.Constant(Activator.CreateInstance(type), type); From acd3e5af96f6cc4a5280f64d7e78181560dcd0ea Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 27 Jul 2026 15:07:32 +0500 Subject: [PATCH 03/31] fix: mark SkipSettings setting setter as Obsolete --- src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs index 79ec8011..f6a168e4 100644 --- a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs +++ b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs @@ -29,6 +29,8 @@ public OverrideTypesSetter SkipAllSettings(bool value) return this; } + [Obsolete("This method will be removed in the release version." + + "It is used for debugging and finding settings that cannot be overridden by existing settings setters.")] public OverrideTypesSetter SkipSettings(params Expression>[] settings) { foreach (var member in settings) From 4bc2a2ed3a663563f2ca61ec0746ee7869b44e5a Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 27 Jul 2026 15:09:25 +0500 Subject: [PATCH 04/31] chore: Bump version to v10.1.0-pre01 --- 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 7b0f4082..b7f0bc75 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ false - 10.0.11 + 10.1.0-pre01 netstandard2.0;net10.0;net9.0;net8.0 netstandard2.0;net10.0;net9.0;net8.0 net10.0;net9.0;net8.0 From 7857b0c94b42955458c93a3d201fd730c8563102 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 28 Jul 2026 13:51:05 +0500 Subject: [PATCH 05/31] feat: add SkipDestinationTransforms() --- src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs | 2 +- src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs index 8ee49827..0f03a13d 100644 --- a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -19,7 +19,7 @@ public void OverrideDestinationTramsformIsWorked() .MapUsing(src => src.Children, dest => dest.Children, cfg => { - cfg.SkipAllSettings(true); + cfg.SkipDestinationTransforms(); }) .MapUsing(src => src.Array, dest => dest.Array, cfg => diff --git a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs index f6a168e4..a6254f4d 100644 --- a/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs +++ b/src/Mapster/TypeAdapterSetters/OverrideTypesSetter.cs @@ -45,6 +45,12 @@ public TypeAdapterSetter ReConfigurate() { return new TypeAdapterSetter(this.Settings,this.Config); } + + public OverrideTypesSetter SkipDestinationTransforms() + { + this.SkipSettings(x => x.DestinationTransforms); + return this; + } } From a246b7a1044a267005ef8f40cb2043acd47e0540 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 29 Jul 2026 10:02:29 +0500 Subject: [PATCH 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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 12/31] 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 13/31] 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 14/31] 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 15/31] 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 16/31] 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 17/31] 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 18/31] 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 19/31] 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(); + } + } +} From b96b98eebb2a7a18b001209b9b8fda91f91ee8e1 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 10 Aug 2026 14:35:19 +0500 Subject: [PATCH 20/31] chore: Bump version to v10.1.0-pre02 --- 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 b7f0bc75..ab75d96d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ false - 10.1.0-pre01 + 10.1.0-pre02 netstandard2.0;net10.0;net9.0;net8.0 netstandard2.0;net10.0;net9.0;net8.0 net10.0;net9.0;net8.0 From 1f9f89c1e3b1765cd06cee5fcd3a5b44794fac52 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 19 Aug 2026 13:26:39 +0500 Subject: [PATCH 21/31] feat: Improvements for MapsterTool GenerateMappers Create Explicit implementation from Mappers interfaces --- .../ExpressionTranslator.cs | 75 +++++++++++++++++++ src/Mapster.Tool/Program.cs | 6 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/ExpressionTranslator/ExpressionTranslator.cs b/src/ExpressionTranslator/ExpressionTranslator.cs index 6c219f86..7e851221 100644 --- a/src/ExpressionTranslator/ExpressionTranslator.cs +++ b/src/ExpressionTranslator/ExpressionTranslator.cs @@ -1268,6 +1268,81 @@ public Expression VisitLambda(LambdaExpression node, LambdaType type, string? me } } + public Expression VisitLambdaForGenerateMappers(LambdaExpression node, LambdaType type, Type InterfaceType, string? methodName = null, + bool isInternal = false) + { + VisitLambda(node, type, methodName, isInternal); + + if (type == LambdaType.PrivateLambda || type == LambdaType.PublicLambda) + { + _inlineCount++; + if (type == LambdaType.PublicLambda) + { + var name = methodName != null ? $"{InterfaceType.FullName}.{methodName}" : "Main"; + WriteLine(); + var funcType = MakeDelegateType(node.ReturnType, node.Parameters.Select(it => it.Type).ToArray()); + var exprType = typeof(Expression<>).MakeGenericType(funcType); + Write(Translate(exprType), " ", name, " => "); + } + + IList args; + if (node.Parameters.Count == 1) + { + args = new List(); + var arg = VisitParameter(node.Parameters[0]); + args.Add((ParameterExpression)arg); + } + else + { + args = VisitArguments("(", node.Parameters.ToList(), p => (ParameterExpression)VisitParameter(p), + ")"); + } + + Write(" => "); + var body = VisitGroup(node.Body, ExpressionType.Quote); + if (type == LambdaType.PublicLambda) + Write(";"); + _inlineCount--; + return Expression.Lambda(body, node.Name, node.TailCall, args); + } + else + { + var name = methodName != null ? $"{InterfaceType.FullName}.{methodName}" : "Main"; + if (type == LambdaType.PublicMethod || type == LambdaType.ExtensionMethod) + { + if (!isInternal) + isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || + node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); + WriteLine(); + Methods[name] = node.Type; + } + else + { + name = GetName(node, name); + WriteModifierNextLine("private"); + } + + Write(Translate(node.ReturnType), " ", name); + var open = "("; + if (type == LambdaType.ExtensionMethod) + { + if (Definitions?.IsStatic != true) + throw new InvalidOperationException("Extension method requires static class"); + if (node.Parameters.Count == 0) + throw new InvalidOperationException("Extension method requires at least 1 parameter"); + open = "(this "; + } + + var args = VisitArguments(open, node.Parameters, VisitParameterDeclaration, ")"); + Indent(); + var body = VisitBody(node.Body, true); + + Outdent(); + + return Expression.Lambda(body, name, node.TailCall, args); + } + } + private HashSet? _visitedLambda; private int _writerLevel; diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index 1347cb13..5e0dacec 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -138,9 +138,10 @@ private static void GenerateMappers(MapperOptions opt) var funcArgs = propArgs.GetGenericArguments(); var tuple = new TypeTuple(funcArgs[0], funcArgs[1]); var expr = config.CreateMapExpression(tuple, MapType.Projection); - translator.VisitLambda( + translator.VisitLambdaForGenerateMappers( expr, ExpressionTranslator.LambdaType.PublicLambda, + @interface, prop.Name ); } @@ -162,9 +163,10 @@ private static void GenerateMappers(MapperOptions opt) tuple, methodArgs.Length == 1 ? MapType.Map : MapType.MapToTarget ); - translator.VisitLambda( + translator.VisitLambdaForGenerateMappers( expr, ExpressionTranslator.LambdaType.PublicMethod, + @interface, method.Name ); } From de497a0c7327e8e58d61559f56b6864028f1395e Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 19 Aug 2026 13:45:55 +0500 Subject: [PATCH 22/31] feat: add GeneratedMapperAttribute and test --- .../ExpressionTranslator.cs | 14 +++- .../IGeneratedAttribute.cs | 11 +++ .../MapsterToolGeneratedMapperAttribute.cs | 14 ++++ .../Helpers/GeneratedBase.cs | 18 ++++ src/ExpressionTranslator/TypeDefinitions.cs | 4 +- src/Mapster.Tool/Program.cs | 19 +++-- src/TemplateTest/CreateMapExpressionTest.cs | 84 +++++++++++++++++++ 7 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs create mode 100644 src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs create mode 100644 src/ExpressionTranslator/Helpers/GeneratedBase.cs diff --git a/src/ExpressionTranslator/ExpressionTranslator.cs b/src/ExpressionTranslator/ExpressionTranslator.cs index 7e851221..e7ed9838 100644 --- a/src/ExpressionTranslator/ExpressionTranslator.cs +++ b/src/ExpressionTranslator/ExpressionTranslator.cs @@ -1940,9 +1940,16 @@ public override string ToString() WriteNextLine("using ", ns, ";"); } - WriteLine(); } + foreach (var ns in Definitions.GeneratedAttributes.Select(x => x.NameSpace).Distinct()) + { + WriteNextLine("using ", ns, ";"); + } + + if(_usings != null || Definitions.GeneratedAttributes.Count != 0) + WriteLine(); + // NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName // keep logic here for compatibility if (_typeNames != null) @@ -1966,6 +1973,11 @@ public override string ToString() Indent(); } + foreach (var gAttr in Definitions.GeneratedAttributes) + { + WriteNextLine(gAttr.Implimentation); + } + var isInternal = Definitions.IsInternal; if (!isInternal) isInternal = Definitions.Implements?.Any(it => diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs new file mode 100644 index 00000000..87803168 --- /dev/null +++ b/src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs @@ -0,0 +1,11 @@ +namespace ExpressionDebugger.Helpers.GeneratedAttributes +{ + public interface IGeneratedAttribute + { + public string NameSpace { get;} + public string Declaration { get;} + public string Implimentation { get; } + public string FileName { get;} + + } +} diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs new file mode 100644 index 00000000..2aed3b57 --- /dev/null +++ b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs @@ -0,0 +1,14 @@ +namespace ExpressionDebugger.Helpers.GeneratedAttributes +{ + public class MapsterToolGeneratedMapperAttribute : GeneratedBase, IGeneratedAttribute + { + public string NameSpace => "Mapster.Generated.Attributes"; + + public string Declaration => + "using System;\r\n\r\nnamespace Mapster.Generated.Attributes\r\n{\r\n public sealed class MapsterToolGeneratedMapperAttribute : Attribute\r\n {\r\n\r\n }\r\n} "; + + public string Implimentation => "[MapsterToolGeneratedMapper]"; + + public string FileName => "MapsterToolGeneratedMapperAttribute"; + } +} diff --git a/src/ExpressionTranslator/Helpers/GeneratedBase.cs b/src/ExpressionTranslator/Helpers/GeneratedBase.cs new file mode 100644 index 00000000..4be95395 --- /dev/null +++ b/src/ExpressionTranslator/Helpers/GeneratedBase.cs @@ -0,0 +1,18 @@ +namespace ExpressionDebugger.Helpers +{ + public abstract class GeneratedBase + { + public override bool Equals(object obj) + { + if(obj is null) + return base.Equals(obj); + else + return this.GetType() == obj.GetType(); + } + + public override int GetHashCode() + { + return this.GetType().GetHashCode(); + } + } +} diff --git a/src/ExpressionTranslator/TypeDefinitions.cs b/src/ExpressionTranslator/TypeDefinitions.cs index a8e20667..ec730304 100644 --- a/src/ExpressionTranslator/TypeDefinitions.cs +++ b/src/ExpressionTranslator/TypeDefinitions.cs @@ -1,4 +1,5 @@ -using System; +using ExpressionDebugger.Helpers.GeneratedAttributes; +using System; using System.Collections.Generic; namespace ExpressionDebugger @@ -12,6 +13,7 @@ public class TypeDefinitions public IEnumerable? Implements { get; set; } public bool PrintFullTypeName { get; set; } public bool IsRecordType { get; set; } + public HashSet GeneratedAttributes { get; set; } = new HashSet(); /// /// Set to 2 to mark all properties as nullable diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index 5e0dacec..a456f2d3 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -1,4 +1,9 @@ -using System; +using CommandLine; +using ExpressionDebugger; +using ExpressionDebugger.Helpers.GeneratedAttributes; +using Mapster.Models; +using Mapster.Utils; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,10 +11,6 @@ using System.Reflection; using System.Runtime.Loader; using System.Text; -using CommandLine; -using ExpressionDebugger; -using Mapster.Models; -using Mapster.Utils; namespace Mapster.Tool { @@ -91,6 +92,8 @@ private static void GenerateMappers(MapperOptions opt) config.SelfContainedCodeGeneration = true; config.Scan(assembly); + var generatedAtrr = new[] { new MapsterToolGeneratedMapperAttribute() }; + foreach (var type in assembly.GetLoadableTypes()) { if (!type.IsInterface) @@ -109,6 +112,7 @@ private static void GenerateMappers(MapperOptions opt) TypeName = attr.Name ?? GetImplName(GetCodeFriendlyTypeName(type)), IsInternal = attr.IsInternal, PrintFullTypeName = opt.PrintFullTypeName, + GeneratedAttributes = new(generatedAtrr) }; var path = GetOutput(opt.Output, segments, definitions.TypeName); @@ -177,6 +181,11 @@ private static void GenerateMappers(MapperOptions opt) : translator.ToString(); WriteFile(code, path); } + + foreach (var item in generatedAtrr) + { + WriteFile(item.Declaration, GetOutput(opt.Output, null, item.FileName)); + } } private static string GetImplName(string name) diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs index 4929bb12..b191b25e 100644 --- a/src/TemplateTest/CreateMapExpressionTest.cs +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -1,7 +1,11 @@ using ExpressionDebugger; +using ExpressionDebugger.Helpers.GeneratedAttributes; using Mapster; +using Mapster.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Collections.Generic; +using System.Linq.Expressions; namespace TemplateTest { @@ -64,6 +68,86 @@ public void TestCreateProjectionExpression() Assert.IsNotNull(code); } + + /// + /// https://github.com/MapsterMapper/Mapster/issues/399 + /// + [TestMethod] + public void TestRegressionMapperGenerationTranslation() + { + var config = new TypeAdapterConfig(); + config.SelfContainedCodeGeneration = true; + + var definitions = new TypeDefinitions + { + Implements = new[] { typeof(IMyTypeMapper) }, + Namespace = "Benchmark", + TypeName = "CustomerMapper", + IsInternal = true, + GeneratedAttributes = new(new[] {new MapsterToolGeneratedMapperAttribute()}) + }; + + var translator = new ExpressionTranslator(definitions); + + foreach (var method in typeof(IMyTypeMapper).GetMethods()) + { + if (method.IsGenericMethod) + continue; + if (method.ReturnType == typeof(void)) + continue; + var methodArgs = method.GetParameters(); + if (methodArgs.Length < 1 || methodArgs.Length > 2) + continue; + var tuple = new TypeTuple(methodArgs[0].ParameterType, method.ReturnType); + var expr = config.CreateMapExpression( + tuple, + methodArgs.Length == 1 ? MapType.Map : MapType.MapToTarget + ); + translator.VisitLambdaForGenerateMappers( + expr, + ExpressionTranslator.LambdaType.PublicMethod, + typeof(IMyTypeMapper), + method.Name + ); + } + + foreach (var prop in typeof(IMyTypeMapper).GetProperties()) + { + if (!prop.PropertyType.IsGenericType) + continue; + if (prop.PropertyType.GetGenericTypeDefinition() != typeof(Expression<>)) + continue; + var propArgs = prop.PropertyType.GetGenericArguments()[0]; + if (!propArgs.IsGenericType) + continue; + if (propArgs.GetGenericTypeDefinition() != typeof(Func<,>)) + continue; + var funcArgs = propArgs.GetGenericArguments(); + var tuple = new TypeTuple(funcArgs[0], funcArgs[1]); + var expr = config.CreateMapExpression(tuple, MapType.Projection); + translator.VisitLambdaForGenerateMappers( + expr, + ExpressionTranslator.LambdaType.PublicLambda, + typeof(IMyTypeMapper), + prop.Name + ); + } + + + var txt = translator.ToString(); + + Assert.IsTrue(txt.Contains("Expression> TemplateTest.IMyTypeMapper.Projection")); + Assert.IsTrue(txt.Contains("AddressDTO TemplateTest.IMyTypeMapper.Map")); + Assert.IsTrue(txt.Contains("[MapsterToolGeneratedMapper]")); + + } + + } + + internal interface IMyTypeMapper + { + AddressDTO Map(Address p1); + Expression> Projection { get; } } public class Address From efb4dddbe84620f4c02d663b7f912b35b4a4542c Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 19 Aug 2026 17:33:51 +0500 Subject: [PATCH 23/31] feat: add helpers file created param --- src/Mapster.Tool/MapperOptions.cs | 3 +++ src/Mapster.Tool/Program.cs | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Mapster.Tool/MapperOptions.cs b/src/Mapster.Tool/MapperOptions.cs index 9cd8a1aa..f4725a9b 100644 --- a/src/Mapster.Tool/MapperOptions.cs +++ b/src/Mapster.Tool/MapperOptions.cs @@ -28,6 +28,9 @@ public class MapperOptions [Option('N', "nullableDirective", Required = false, HelpText = "Set true to add \"#nullable enable\" to the top of generated mapper files")] public bool GenerateNullableDirective { get; set; } + [Option('H', "createHelpers", Required = false, HelpText = "Set true to create helper files")] + public bool CreateHelpers { get; set; } + [Usage(ApplicationAlias = "dotnet mapster mapper")] public static IEnumerable Examples => new List diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index a456f2d3..b20ae4c9 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -112,9 +112,12 @@ private static void GenerateMappers(MapperOptions opt) TypeName = attr.Name ?? GetImplName(GetCodeFriendlyTypeName(type)), IsInternal = attr.IsInternal, PrintFullTypeName = opt.PrintFullTypeName, - GeneratedAttributes = new(generatedAtrr) + }; + if (opt.CreateHelpers) + definitions.GeneratedAttributes = new(generatedAtrr); + var path = GetOutput(opt.Output, segments, definitions.TypeName); if (opt.SkipExistingFiles && File.Exists(path)) { @@ -182,9 +185,12 @@ private static void GenerateMappers(MapperOptions opt) WriteFile(code, path); } - foreach (var item in generatedAtrr) + if (opt.CreateHelpers) { - WriteFile(item.Declaration, GetOutput(opt.Output, null, item.FileName)); + foreach (var item in generatedAtrr) + { + WriteFile(item.Declaration, GetOutput(opt.Output, null, item.FileName)); + } } } From 6312229fc4b098e189d05ce5a4c0092fc93db74f Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 19 Aug 2026 19:35:50 +0500 Subject: [PATCH 24/31] feat: add random --- .../MapsterToolGeneratedMapperAttribute.cs | 30 ++++++++-- .../Helpers/RandomNamespaceGenerator.cs | 56 +++++++++++++++++++ src/TemplateTest/CreateMapExpressionTest.cs | 2 + 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs index 2aed3b57..18ea2e97 100644 --- a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs +++ b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs @@ -1,14 +1,36 @@ -namespace ExpressionDebugger.Helpers.GeneratedAttributes +using System.Text; + +namespace ExpressionDebugger.Helpers.GeneratedAttributes { public class MapsterToolGeneratedMapperAttribute : GeneratedBase, IGeneratedAttribute { - public string NameSpace => "Mapster.Generated.Attributes"; + private readonly StringBuilder _Declaration; + private readonly bool _isRandomNameSpace; + private readonly string _NameSpace; + + public string NameSpace => _NameSpace; - public string Declaration => - "using System;\r\n\r\nnamespace Mapster.Generated.Attributes\r\n{\r\n public sealed class MapsterToolGeneratedMapperAttribute : Attribute\r\n {\r\n\r\n }\r\n} "; + public string Declaration => _Declaration.ToString(); public string Implimentation => "[MapsterToolGeneratedMapper]"; public string FileName => "MapsterToolGeneratedMapperAttribute"; + + public MapsterToolGeneratedMapperAttribute(bool isRandomNameSpace = false) + { + _isRandomNameSpace = isRandomNameSpace; + + if (_isRandomNameSpace) + _NameSpace = $"Mapster.Generated.Attributes.{RandomNamespaceGenerator.Generate(1,1)}"; + else + _NameSpace = "Mapster.Generated.Attributes"; + + _Declaration = new StringBuilder(); + + _Declaration.Append("using System;\r\n\r\n"); + _Declaration.Append($"namespace {NameSpace}"); + _Declaration.Append("\r\n{\r\n public sealed class MapsterToolGeneratedMapperAttribute : Attribute\r\n {\r\n\r\n }\r\n} "); + } + } } diff --git a/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs b/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs new file mode 100644 index 00000000..4548a765 --- /dev/null +++ b/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs @@ -0,0 +1,56 @@ +using System; +using System.Text; + +namespace ExpressionDebugger.Helpers +{ + public static class RandomNamespaceGenerator + { + private static readonly Random _random = new Random(); + private const string Consonants = "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ"; + private const string Vowels = "aeiouAEIOU"; + private const string Digits = "0123456789"; + + public static string Generate(int minParts = 2, int maxParts = 4) + { + if (minParts < 1) minParts = 1; + if (maxParts < minParts) maxParts = minParts; + + int partsCount = _random.Next(minParts, maxParts + 1); + var sb = new StringBuilder(); + + for (int i = 0; i < partsCount; i++) + { + if (i > 0) sb.Append('.'); + sb.Append(GeneratePart()); + } + + return sb.ToString(); + } + + private static string GeneratePart(int minLength = 2, int maxLength = 10) + { + if (minLength < 1) minLength = 1; + if (maxLength < minLength) maxLength = minLength; + + int length = _random.Next(minLength, maxLength + 1); + var sb = new StringBuilder(length); + + sb.Append(Consonants[_random.Next(Consonants.Length)]); + + for (int i = 1; i < length; i++) + { + string pool = (i % 2 == 0) ? Vowels : Consonants; + + if (_random.NextDouble() < 0.1) + { + pool = Digits; + } + + sb.Append(pool[_random.Next(pool.Length)]); + } + + return sb.ToString(); + } + } +} + diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs index b191b25e..514229aa 100644 --- a/src/TemplateTest/CreateMapExpressionTest.cs +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -75,6 +75,8 @@ public void TestCreateProjectionExpression() [TestMethod] public void TestRegressionMapperGenerationTranslation() { + var S = new MapsterToolGeneratedMapperAttribute(true); + var config = new TypeAdapterConfig(); config.SelfContainedCodeGeneration = true; From 2a7509cdea7c3f93aaeed633456b0798f75646d4 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 20 Aug 2026 21:19:24 +0500 Subject: [PATCH 25/31] feat: add supported generate mappers for internal interfaces and public interfaces with internal member and test refactoring --- .../Helpers/MemberInfoExtensions.cs | 33 +++ src/Mapster.Tool/Program.cs | 17 +- src/TemplateTest/CreateMapExpressionTest.cs | 188 +++++++++++++----- 3 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs diff --git a/src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs b/src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs new file mode 100644 index 00000000..b3a1782c --- /dev/null +++ b/src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; + +namespace ExpressionDebugger.Helpers +{ + public static class MemberInfoExtensions + { + public static bool IsPublicOrInternal(this MethodInfo method) + { + if (method == null) throw new ArgumentNullException(nameof(method)); + + return !method.IsPrivate + && !method.IsFamily + && !method.IsFamilyOrAssembly + && !method.IsFamilyAndAssembly + && (method.IsPublic || true); + } + + + + public static bool IsGetterPublicOrInternal(this PropertyInfo property) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + + MethodInfo? getMethod = property.GetMethod; + + if (getMethod == null) return false; + + return getMethod.IsPublicOrInternal(); + } + } + +} diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index b20ae4c9..79534284 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -1,5 +1,6 @@ using CommandLine; using ExpressionDebugger; +using ExpressionDebugger.Helpers; using ExpressionDebugger.Helpers.GeneratedAttributes; using Mapster.Models; using Mapster.Utils; @@ -118,6 +119,8 @@ private static void GenerateMappers(MapperOptions opt) if (opt.CreateHelpers) definitions.GeneratedAttributes = new(generatedAtrr); + bool? _isForceInternal = definitions.IsInternal ? true : null; + var path = GetOutput(opt.Output, segments, definitions.TypeName); if (opt.SkipExistingFiles && File.Exists(path)) { @@ -131,7 +134,9 @@ private static void GenerateMappers(MapperOptions opt) var interfaces = type.GetAllInterfaces(); foreach (var @interface in interfaces) { - foreach (var prop in @interface.GetProperties()) + foreach (var prop in @interface.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(x => x.IsGetterPublicOrInternal()) + ) { if (!prop.PropertyType.IsGenericType) continue; @@ -149,14 +154,17 @@ private static void GenerateMappers(MapperOptions opt) expr, ExpressionTranslator.LambdaType.PublicLambda, @interface, - prop.Name + prop.Name, + _isForceInternal ?? (!prop.GetMethod?.IsPublic ?? false) ); } } foreach (var @interface in interfaces) { - foreach (var method in @interface.GetMethods()) + foreach (var method in @interface.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(x => x.IsPublicOrInternal()) + ) { if (method.IsGenericMethod) continue; @@ -174,7 +182,8 @@ private static void GenerateMappers(MapperOptions opt) expr, ExpressionTranslator.LambdaType.PublicMethod, @interface, - method.Name + method.Name, + _isForceInternal ?? !method.IsPublic ); } } diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs index 514229aa..928adc32 100644 --- a/src/TemplateTest/CreateMapExpressionTest.cs +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -1,11 +1,14 @@ using ExpressionDebugger; +using ExpressionDebugger.Helpers; using ExpressionDebugger.Helpers.GeneratedAttributes; using Mapster; using Mapster.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; +using System.Reflection; namespace TemplateTest { @@ -82,74 +85,95 @@ public void TestRegressionMapperGenerationTranslation() var definitions = new TypeDefinitions { - Implements = new[] { typeof(IMyTypeMapper) }, + Implements = new[] { typeof(IMyTypeMapper), typeof(IMyTypeMapperIntenal) }, Namespace = "Benchmark", TypeName = "CustomerMapper", - IsInternal = true, + IsInternal = false, GeneratedAttributes = new(new[] {new MapsterToolGeneratedMapperAttribute()}) }; var translator = new ExpressionTranslator(definitions); - foreach (var method in typeof(IMyTypeMapper).GetMethods()) - { - if (method.IsGenericMethod) - continue; - if (method.ReturnType == typeof(void)) - continue; - var methodArgs = method.GetParameters(); - if (methodArgs.Length < 1 || methodArgs.Length > 2) - continue; - var tuple = new TypeTuple(methodArgs[0].ParameterType, method.ReturnType); - var expr = config.CreateMapExpression( - tuple, - methodArgs.Length == 1 ? MapType.Map : MapType.MapToTarget - ); - translator.VisitLambdaForGenerateMappers( - expr, - ExpressionTranslator.LambdaType.PublicMethod, - typeof(IMyTypeMapper), - method.Name - ); - } + translator.CreateFromInterface(definitions, config); + + var code = translator.ToString(); + + Assert.IsTrue(code.Contains("public partial class CustomerMapper")); // mapper class is public + + Assert.IsTrue(code.Contains("Expression> TemplateTest.IMyTypeMapper.Projection")); + Assert.IsTrue(code.Contains("AddressDTO TemplateTest.IMyTypeMapper.Map")); + Assert.IsTrue(code.Contains("[MapsterToolGeneratedMapper]")); + + Assert.IsTrue(code.Contains("internal AddressDTO Map")); // create internal method in public interface + + // create as internal because declarate in internal interface and using internal type AddressInternal + Assert.IsTrue(code.Contains("internal AddressInternal MapInternal")); + Assert.IsTrue(code.Contains("internal Expression> ProjectionInternal")); + + + Assert.IsTrue(code.Contains("public AddressDTO MapPublicClassInInternalInterface")); // create public method in internal interface because using public types + + // method using public types in internal interface but marked as internal create as internal method + Assert.IsTrue(code.Contains("internal AddressDTO MapPublicClassInInternalInterfaceWithMarkInternal")); + } + + [TestMethod] + public void CreateForceInternalMapper() + { + var config = new TypeAdapterConfig(); + config.SelfContainedCodeGeneration = true; - foreach (var prop in typeof(IMyTypeMapper).GetProperties()) + var definitions = new TypeDefinitions { - if (!prop.PropertyType.IsGenericType) - continue; - if (prop.PropertyType.GetGenericTypeDefinition() != typeof(Expression<>)) - continue; - var propArgs = prop.PropertyType.GetGenericArguments()[0]; - if (!propArgs.IsGenericType) - continue; - if (propArgs.GetGenericTypeDefinition() != typeof(Func<,>)) - continue; - var funcArgs = propArgs.GetGenericArguments(); - var tuple = new TypeTuple(funcArgs[0], funcArgs[1]); - var expr = config.CreateMapExpression(tuple, MapType.Projection); - translator.VisitLambdaForGenerateMappers( - expr, - ExpressionTranslator.LambdaType.PublicLambda, - typeof(IMyTypeMapper), - prop.Name - ); - } - + Implements = new[] { typeof(IMyTypeMapperForce)}, + Namespace = "Benchmark", + TypeName = "CustomerMapper", + IsInternal = true, // force create internal mapper + GeneratedAttributes = new(new[] { new MapsterToolGeneratedMapperAttribute() }) + }; + + var translator = new ExpressionTranslator(definitions); + + translator.CreateFromInterface(definitions, config); - var txt = translator.ToString(); + var code = translator.ToString(); - Assert.IsTrue(txt.Contains("Expression> TemplateTest.IMyTypeMapper.Projection")); - Assert.IsTrue(txt.Contains("AddressDTO TemplateTest.IMyTypeMapper.Map")); - Assert.IsTrue(txt.Contains("[MapsterToolGeneratedMapper]")); + Assert.IsTrue(code.Contains("internal partial class CustomerMapper")); // mapper class is internal + // force create internal method using only public types because mapper class is internal + Assert.IsTrue(code.Contains("internal AddressDTO Map")); } + + + } + + + public interface IMyTypeMapper + { + internal AddressDTO Map(Address p1); + public Expression> Projection { get; } + } + + internal interface IMyTypeMapperIntenal + { + AddressInternal MapInternal(Address p1); + Expression> ProjectionInternal { get; } + AddressDTO MapPublicClassInInternalInterface(Address p1); + internal AddressDTO MapPublicClassInInternalInterfaceWithMarkInternal(Address p1); } - internal interface IMyTypeMapper + public interface IMyTypeMapperForce { AddressDTO Map(Address p1); - Expression> Projection { get; } + } + + internal class AddressInternal + { + public int Id { get; set; } + public string Street { get; set; } + public string City { get; set; } + public string Country { get; set; } } public class Address @@ -188,4 +212,68 @@ public class CustomerDTO public List WorkAddresses { get; set; } public string AddressCity { get; set; } } + + static class GenerateMappersExtensions + { + public static void CreateFromInterface(this ExpressionTranslator translator, TypeDefinitions definitions, TypeAdapterConfig config) + { + if (definitions.Implements == null) + return; + + foreach (var interfaceType in definitions.Implements) + { + bool? _isForceInternal = definitions.IsInternal ? true : null; + + foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(x => x.IsPublicOrInternal()) + ) + { + if (method.IsGenericMethod) + continue; + if (method.ReturnType == typeof(void)) + continue; + var methodArgs = method.GetParameters(); + if (methodArgs.Length < 1 || methodArgs.Length > 2) + continue; + var tuple = new TypeTuple(methodArgs[0].ParameterType, method.ReturnType); + var expr = config.CreateMapExpression( + tuple, + methodArgs.Length == 1 ? MapType.Map : MapType.MapToTarget + ); + translator.VisitLambdaForGenerateMappers( + expr, + ExpressionTranslator.LambdaType.PublicMethod, + interfaceType, + method.Name, + _isForceInternal ?? !method.IsPublic + ); + } + + foreach (var prop in interfaceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(x => x.IsGetterPublicOrInternal()) + ) + { + if (!prop.PropertyType.IsGenericType) + continue; + if (prop.PropertyType.GetGenericTypeDefinition() != typeof(Expression<>)) + continue; + var propArgs = prop.PropertyType.GetGenericArguments()[0]; + if (!propArgs.IsGenericType) + continue; + if (propArgs.GetGenericTypeDefinition() != typeof(Func<,>)) + continue; + var funcArgs = propArgs.GetGenericArguments(); + var tuple = new TypeTuple(funcArgs[0], funcArgs[1]); + var expr = config.CreateMapExpression(tuple, MapType.Projection); + translator.VisitLambdaForGenerateMappers( + expr, + ExpressionTranslator.LambdaType.PublicLambda, + interfaceType, + prop.Name, + _isForceInternal ?? (!prop.GetMethod?.IsPublic ?? false) + ); + } + } + } + } } \ No newline at end of file From 83e275057b2a298c2af5687f95f533bd71bb0426 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 21 Aug 2026 00:59:08 +0500 Subject: [PATCH 26/31] fix: refactoring attribute impl --- .../MapsterToolGeneratedMapperAttribute.cs | 16 +++++++--------- src/Mapster.Tool/MapperOptions.cs | 4 ++-- src/Mapster.Tool/Program.cs | 19 +++++++++---------- src/TemplateTest/CreateMapExpressionTest.cs | 6 +++--- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs index 18ea2e97..2f8c91de 100644 --- a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs +++ b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs @@ -1,11 +1,11 @@ -using System.Text; +using System; +using System.Text; namespace ExpressionDebugger.Helpers.GeneratedAttributes { public class MapsterToolGeneratedMapperAttribute : GeneratedBase, IGeneratedAttribute { private readonly StringBuilder _Declaration; - private readonly bool _isRandomNameSpace; private readonly string _NameSpace; public string NameSpace => _NameSpace; @@ -16,15 +16,13 @@ public class MapsterToolGeneratedMapperAttribute : GeneratedBase, IGeneratedAttr public string FileName => "MapsterToolGeneratedMapperAttribute"; - public MapsterToolGeneratedMapperAttribute(bool isRandomNameSpace = false) + public MapsterToolGeneratedMapperAttribute(string extendedNameSpace) { - _isRandomNameSpace = isRandomNameSpace; - - if (_isRandomNameSpace) - _NameSpace = $"Mapster.Generated.Attributes.{RandomNamespaceGenerator.Generate(1,1)}"; - else - _NameSpace = "Mapster.Generated.Attributes"; + if (String.IsNullOrEmpty(extendedNameSpace)) + throw new ArgumentNullException("Extended namespace not specified or is null/empty string"); + _NameSpace = $"Mapster.Generated.Attributes.{extendedNameSpace}"; + _Declaration = new StringBuilder(); _Declaration.Append("using System;\r\n\r\n"); diff --git a/src/Mapster.Tool/MapperOptions.cs b/src/Mapster.Tool/MapperOptions.cs index f4725a9b..5ae244a1 100644 --- a/src/Mapster.Tool/MapperOptions.cs +++ b/src/Mapster.Tool/MapperOptions.cs @@ -28,8 +28,8 @@ public class MapperOptions [Option('N', "nullableDirective", Required = false, HelpText = "Set true to add \"#nullable enable\" to the top of generated mapper files")] public bool GenerateNullableDirective { get; set; } - [Option('H', "createHelpers", Required = false, HelpText = "Set true to create helper files")] - public bool CreateHelpers { get; set; } + [Option('H', "extNamespace", Required = false, HelpText = "Specify namespace to activate and generate additional features")] + public string? CreateHelpers { get; set; } [Usage(ApplicationAlias = "dotnet mapster mapper")] public static IEnumerable Examples => diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index 79534284..aa46d3eb 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -93,7 +93,11 @@ private static void GenerateMappers(MapperOptions opt) config.SelfContainedCodeGeneration = true; config.Scan(assembly); - var generatedAtrr = new[] { new MapsterToolGeneratedMapperAttribute() }; + var generatedAtrr = new List(); + + if (!String.IsNullOrEmpty(opt.CreateHelpers)) + generatedAtrr.Add(new MapsterToolGeneratedMapperAttribute(opt.CreateHelpers)); + foreach (var type in assembly.GetLoadableTypes()) { @@ -113,12 +117,9 @@ private static void GenerateMappers(MapperOptions opt) TypeName = attr.Name ?? GetImplName(GetCodeFriendlyTypeName(type)), IsInternal = attr.IsInternal, PrintFullTypeName = opt.PrintFullTypeName, - + GeneratedAttributes = new(generatedAtrr) }; - if (opt.CreateHelpers) - definitions.GeneratedAttributes = new(generatedAtrr); - bool? _isForceInternal = definitions.IsInternal ? true : null; var path = GetOutput(opt.Output, segments, definitions.TypeName); @@ -194,12 +195,10 @@ private static void GenerateMappers(MapperOptions opt) WriteFile(code, path); } - if (opt.CreateHelpers) + + foreach (var item in generatedAtrr) { - foreach (var item in generatedAtrr) - { - WriteFile(item.Declaration, GetOutput(opt.Output, null, item.FileName)); - } + WriteFile(item.Declaration, GetOutput(opt.Output, null, item.FileName)); } } diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs index 928adc32..b325b333 100644 --- a/src/TemplateTest/CreateMapExpressionTest.cs +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -78,7 +78,7 @@ public void TestCreateProjectionExpression() [TestMethod] public void TestRegressionMapperGenerationTranslation() { - var S = new MapsterToolGeneratedMapperAttribute(true); + var S = new MapsterToolGeneratedMapperAttribute("Test"); var config = new TypeAdapterConfig(); config.SelfContainedCodeGeneration = true; @@ -89,7 +89,7 @@ public void TestRegressionMapperGenerationTranslation() Namespace = "Benchmark", TypeName = "CustomerMapper", IsInternal = false, - GeneratedAttributes = new(new[] {new MapsterToolGeneratedMapperAttribute()}) + GeneratedAttributes = new(new[] {new MapsterToolGeneratedMapperAttribute("Test") }) }; var translator = new ExpressionTranslator(definitions); @@ -129,7 +129,7 @@ public void CreateForceInternalMapper() Namespace = "Benchmark", TypeName = "CustomerMapper", IsInternal = true, // force create internal mapper - GeneratedAttributes = new(new[] { new MapsterToolGeneratedMapperAttribute() }) + GeneratedAttributes = new(new[] { new MapsterToolGeneratedMapperAttribute("Test") }) }; var translator = new ExpressionTranslator(definitions); From 7cf555302fb09d29656914b3908ef29c7a440409 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 21 Aug 2026 01:57:54 +0500 Subject: [PATCH 27/31] fix: skip explicit interface implimentation if using only public way: 1) property or method maks as public 2) property or method using only public clases 3) not using IsInternal atrribute --- src/ExpressionTranslator/ExpressionTranslator.cs | 8 ++++++++ src/TemplateTest/CreateMapExpressionTest.cs | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ExpressionTranslator/ExpressionTranslator.cs b/src/ExpressionTranslator/ExpressionTranslator.cs index e7ed9838..823bebcc 100644 --- a/src/ExpressionTranslator/ExpressionTranslator.cs +++ b/src/ExpressionTranslator/ExpressionTranslator.cs @@ -11,6 +11,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; +using System.Xml.Linq; namespace ExpressionDebugger { @@ -1273,6 +1274,13 @@ public Expression VisitLambdaForGenerateMappers(LambdaExpression node, LambdaTyp { VisitLambda(node, type, methodName, isInternal); + if (!isInternal) + isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || + node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); + + if(!isInternal) + return node; // skip create interface implimentation if public only + if (type == LambdaType.PrivateLambda || type == LambdaType.PublicLambda) { _inlineCount++; diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs index b325b333..d71ab9d7 100644 --- a/src/TemplateTest/CreateMapExpressionTest.cs +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -99,8 +99,6 @@ public void TestRegressionMapperGenerationTranslation() var code = translator.ToString(); Assert.IsTrue(code.Contains("public partial class CustomerMapper")); // mapper class is public - - Assert.IsTrue(code.Contains("Expression> TemplateTest.IMyTypeMapper.Projection")); Assert.IsTrue(code.Contains("AddressDTO TemplateTest.IMyTypeMapper.Map")); Assert.IsTrue(code.Contains("[MapsterToolGeneratedMapper]")); From f80678ecb2e13f6912ca42bd27448c397354348d Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 25 Aug 2026 08:26:33 +0500 Subject: [PATCH 28/31] fix: refactoring RandomNamespaceGenerator - Added feature to deterministically generate a valid namespace based on an invalid string --- .../MapsterToolGeneratedMapperAttribute.cs | 8 +++- .../Helpers/RandomNamespaceGenerator.cs | 48 ++++++++++++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs index 2f8c91de..c2b3d9fc 100644 --- a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs +++ b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using static ExpressionDebugger.Helpers.RandomNamespaceGenerator; namespace ExpressionDebugger.Helpers.GeneratedAttributes { @@ -20,9 +21,12 @@ public MapsterToolGeneratedMapperAttribute(string extendedNameSpace) { if (String.IsNullOrEmpty(extendedNameSpace)) throw new ArgumentNullException("Extended namespace not specified or is null/empty string"); - - _NameSpace = $"Mapster.Generated.Attributes.{extendedNameSpace}"; + if(CheckNameSpace.IsMatch(extendedNameSpace)) + _NameSpace = $"Mapster.Generated.Attributes.{extendedNameSpace}"; + else + _NameSpace = $"Mapster.Generated.Attributes.{Generate(extendedNameSpace,1,1)}"; + _Declaration = new StringBuilder(); _Declaration.Append("using System;\r\n\r\n"); diff --git a/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs b/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs index 4548a765..d02cc84e 100644 --- a/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs +++ b/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs @@ -1,19 +1,48 @@ using System; +using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; namespace ExpressionDebugger.Helpers { public static class RandomNamespaceGenerator { - private static readonly Random _random = new Random(); + public static readonly Regex CheckNameSpace = new Regex(@"^([a-zA-Z_]\w*)(\.[a-zA-Z_]\w*)*$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private const string Consonants = "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ"; private const string Vowels = "aeiouAEIOU"; private const string Digits = "0123456789"; + public static string Generate(string input, int minParts = 2, int maxParts = 4) + { + if (string.IsNullOrEmpty(input)) throw new ArgumentException("Input cannot be empty."); + if (minParts < 1) minParts = 1; + if (maxParts < minParts) maxParts = minParts; + + using var sha256 = SHA256.Create(); + byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); + + long seed = BitConverter.ToInt64(hashBytes, 0); + var random = new Random(unchecked((int)seed ^ (int)(seed >> 32))); + + int partsCount = random.Next(minParts, maxParts + 1); + var sb = new StringBuilder(); + + for (int i = 0; i < partsCount; i++) + { + if (i > 0) sb.Append('.'); + sb.Append(GeneratePart(random)); + } + + return sb.ToString(); + } + + public static string Generate(int minParts = 2, int maxParts = 4) { if (minParts < 1) minParts = 1; if (maxParts < minParts) maxParts = minParts; + + var _random = new Random(); int partsCount = _random.Next(minParts, maxParts + 1); var sb = new StringBuilder(); @@ -21,32 +50,27 @@ public static string Generate(int minParts = 2, int maxParts = 4) for (int i = 0; i < partsCount; i++) { if (i > 0) sb.Append('.'); - sb.Append(GeneratePart()); + sb.Append(GeneratePart(_random)); } return sb.ToString(); } - private static string GeneratePart(int minLength = 2, int maxLength = 10) + private static string GeneratePart(Random random, int minLength = 2, int maxLength = 10) { if (minLength < 1) minLength = 1; if (maxLength < minLength) maxLength = minLength; - int length = _random.Next(minLength, maxLength + 1); + int length = random.Next(minLength, maxLength + 1); var sb = new StringBuilder(length); - sb.Append(Consonants[_random.Next(Consonants.Length)]); + sb.Append(Consonants[random.Next(Consonants.Length)]); for (int i = 1; i < length; i++) { string pool = (i % 2 == 0) ? Vowels : Consonants; - - if (_random.NextDouble() < 0.1) - { - pool = Digits; - } - - sb.Append(pool[_random.Next(pool.Length)]); + if (random.NextDouble() < 0.1) pool = Digits; + sb.Append(pool[random.Next(pool.Length)]); } return sb.ToString(); From 1b263b96e182e4d345c348e0909b7d39608c94f4 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 25 Aug 2026 09:06:55 +0500 Subject: [PATCH 29/31] fix: refactoring helpers generation --- src/Mapster.Tool/MapperOptions.cs | 8 ++++++-- src/Mapster.Tool/Program.cs | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Mapster.Tool/MapperOptions.cs b/src/Mapster.Tool/MapperOptions.cs index 5ae244a1..ce0b6fc5 100644 --- a/src/Mapster.Tool/MapperOptions.cs +++ b/src/Mapster.Tool/MapperOptions.cs @@ -28,8 +28,12 @@ public class MapperOptions [Option('N', "nullableDirective", Required = false, HelpText = "Set true to add \"#nullable enable\" to the top of generated mapper files")] public bool GenerateNullableDirective { get; set; } - [Option('H', "extNamespace", Required = false, HelpText = "Specify namespace to activate and generate additional features")] - public string? CreateHelpers { get; set; } + [Option('h', "helpersCreate", Required = false, HelpText = "Generate helpers features")] + public bool CreateHelpers { get; set; } + + [Option('H', "helpersNamespace", Required = false, HelpText = "Specify additional namespace to generated helpers features")] + public string? HelpersNamespace { get; set; } + [Usage(ApplicationAlias = "dotnet mapster mapper")] public static IEnumerable Examples => diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index aa46d3eb..e93ce4d4 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -95,8 +95,10 @@ private static void GenerateMappers(MapperOptions opt) var generatedAtrr = new List(); - if (!String.IsNullOrEmpty(opt.CreateHelpers)) - generatedAtrr.Add(new MapsterToolGeneratedMapperAttribute(opt.CreateHelpers)); + if (opt.CreateHelpers) + generatedAtrr.Add(new MapsterToolGeneratedMapperAttribute( + opt.HelpersNamespace ?? Path.GetFileNameWithoutExtension(opt.Assembly) + )); foreach (var type in assembly.GetLoadableTypes()) From 6cccb633d201a9f3ac9a97002abcf1c4cb2140c2 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 25 Aug 2026 13:56:53 +0500 Subject: [PATCH 30/31] chore: Bump version to v10.0.13-pre01 --- 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 99ef89c6..50545c59 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ false - 10.0.12 + 10.0.13-pre01 netstandard2.0;net10.0;net9.0;net8.0 netstandard2.0;net10.0;net9.0;net8.0 net10.0;net9.0;net8.0 From a2bb32f0e127df394f9ab063d88fa312554babdb Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 25 Aug 2026 15:17:18 +0500 Subject: [PATCH 31/31] Revert "Improvements for Mapster Tool " --- .../ExpressionTranslator.cs | 97 +--------- .../IGeneratedAttribute.cs | 11 -- .../MapsterToolGeneratedMapperAttribute.cs | 38 ---- .../Helpers/GeneratedBase.cs | 18 -- .../Helpers/MemberInfoExtensions.cs | 33 ---- .../Helpers/RandomNamespaceGenerator.cs | 80 -------- src/ExpressionTranslator/TypeDefinitions.cs | 4 +- src/Mapster.Tool/MapperOptions.cs | 7 - src/Mapster.Tool/Program.cs | 49 ++--- src/TemplateTest/CreateMapExpressionTest.cs | 172 ------------------ 10 files changed, 13 insertions(+), 496 deletions(-) delete mode 100644 src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs delete mode 100644 src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs delete mode 100644 src/ExpressionTranslator/Helpers/GeneratedBase.cs delete mode 100644 src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs delete mode 100644 src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs diff --git a/src/ExpressionTranslator/ExpressionTranslator.cs b/src/ExpressionTranslator/ExpressionTranslator.cs index 823bebcc..6c219f86 100644 --- a/src/ExpressionTranslator/ExpressionTranslator.cs +++ b/src/ExpressionTranslator/ExpressionTranslator.cs @@ -11,7 +11,6 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using System.Xml.Linq; namespace ExpressionDebugger { @@ -1269,88 +1268,6 @@ public Expression VisitLambda(LambdaExpression node, LambdaType type, string? me } } - public Expression VisitLambdaForGenerateMappers(LambdaExpression node, LambdaType type, Type InterfaceType, string? methodName = null, - bool isInternal = false) - { - VisitLambda(node, type, methodName, isInternal); - - if (!isInternal) - isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || - node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); - - if(!isInternal) - return node; // skip create interface implimentation if public only - - if (type == LambdaType.PrivateLambda || type == LambdaType.PublicLambda) - { - _inlineCount++; - if (type == LambdaType.PublicLambda) - { - var name = methodName != null ? $"{InterfaceType.FullName}.{methodName}" : "Main"; - WriteLine(); - var funcType = MakeDelegateType(node.ReturnType, node.Parameters.Select(it => it.Type).ToArray()); - var exprType = typeof(Expression<>).MakeGenericType(funcType); - Write(Translate(exprType), " ", name, " => "); - } - - IList args; - if (node.Parameters.Count == 1) - { - args = new List(); - var arg = VisitParameter(node.Parameters[0]); - args.Add((ParameterExpression)arg); - } - else - { - args = VisitArguments("(", node.Parameters.ToList(), p => (ParameterExpression)VisitParameter(p), - ")"); - } - - Write(" => "); - var body = VisitGroup(node.Body, ExpressionType.Quote); - if (type == LambdaType.PublicLambda) - Write(";"); - _inlineCount--; - return Expression.Lambda(body, node.Name, node.TailCall, args); - } - else - { - var name = methodName != null ? $"{InterfaceType.FullName}.{methodName}" : "Main"; - if (type == LambdaType.PublicMethod || type == LambdaType.ExtensionMethod) - { - if (!isInternal) - isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || - node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); - WriteLine(); - Methods[name] = node.Type; - } - else - { - name = GetName(node, name); - WriteModifierNextLine("private"); - } - - Write(Translate(node.ReturnType), " ", name); - var open = "("; - if (type == LambdaType.ExtensionMethod) - { - if (Definitions?.IsStatic != true) - throw new InvalidOperationException("Extension method requires static class"); - if (node.Parameters.Count == 0) - throw new InvalidOperationException("Extension method requires at least 1 parameter"); - open = "(this "; - } - - var args = VisitArguments(open, node.Parameters, VisitParameterDeclaration, ")"); - Indent(); - var body = VisitBody(node.Body, true); - - Outdent(); - - return Expression.Lambda(body, name, node.TailCall, args); - } - } - private HashSet? _visitedLambda; private int _writerLevel; @@ -1948,15 +1865,8 @@ public override string ToString() WriteNextLine("using ", ns, ";"); } - } - - foreach (var ns in Definitions.GeneratedAttributes.Select(x => x.NameSpace).Distinct()) - { - WriteNextLine("using ", ns, ";"); - } - - if(_usings != null || Definitions.GeneratedAttributes.Count != 0) WriteLine(); + } // NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName // keep logic here for compatibility @@ -1981,11 +1891,6 @@ public override string ToString() Indent(); } - foreach (var gAttr in Definitions.GeneratedAttributes) - { - WriteNextLine(gAttr.Implimentation); - } - var isInternal = Definitions.IsInternal; if (!isInternal) isInternal = Definitions.Implements?.Any(it => diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs deleted file mode 100644 index 87803168..00000000 --- a/src/ExpressionTranslator/Helpers/GeneratedAttributes/IGeneratedAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace ExpressionDebugger.Helpers.GeneratedAttributes -{ - public interface IGeneratedAttribute - { - public string NameSpace { get;} - public string Declaration { get;} - public string Implimentation { get; } - public string FileName { get;} - - } -} diff --git a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs b/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs deleted file mode 100644 index c2b3d9fc..00000000 --- a/src/ExpressionTranslator/Helpers/GeneratedAttributes/MapsterToolGeneratedMapperAttribute.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Text; -using static ExpressionDebugger.Helpers.RandomNamespaceGenerator; - -namespace ExpressionDebugger.Helpers.GeneratedAttributes -{ - public class MapsterToolGeneratedMapperAttribute : GeneratedBase, IGeneratedAttribute - { - private readonly StringBuilder _Declaration; - private readonly string _NameSpace; - - public string NameSpace => _NameSpace; - - public string Declaration => _Declaration.ToString(); - - public string Implimentation => "[MapsterToolGeneratedMapper]"; - - public string FileName => "MapsterToolGeneratedMapperAttribute"; - - public MapsterToolGeneratedMapperAttribute(string extendedNameSpace) - { - if (String.IsNullOrEmpty(extendedNameSpace)) - throw new ArgumentNullException("Extended namespace not specified or is null/empty string"); - - if(CheckNameSpace.IsMatch(extendedNameSpace)) - _NameSpace = $"Mapster.Generated.Attributes.{extendedNameSpace}"; - else - _NameSpace = $"Mapster.Generated.Attributes.{Generate(extendedNameSpace,1,1)}"; - - _Declaration = new StringBuilder(); - - _Declaration.Append("using System;\r\n\r\n"); - _Declaration.Append($"namespace {NameSpace}"); - _Declaration.Append("\r\n{\r\n public sealed class MapsterToolGeneratedMapperAttribute : Attribute\r\n {\r\n\r\n }\r\n} "); - } - - } -} diff --git a/src/ExpressionTranslator/Helpers/GeneratedBase.cs b/src/ExpressionTranslator/Helpers/GeneratedBase.cs deleted file mode 100644 index 4be95395..00000000 --- a/src/ExpressionTranslator/Helpers/GeneratedBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace ExpressionDebugger.Helpers -{ - public abstract class GeneratedBase - { - public override bool Equals(object obj) - { - if(obj is null) - return base.Equals(obj); - else - return this.GetType() == obj.GetType(); - } - - public override int GetHashCode() - { - return this.GetType().GetHashCode(); - } - } -} diff --git a/src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs b/src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs deleted file mode 100644 index b3a1782c..00000000 --- a/src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Reflection; - -namespace ExpressionDebugger.Helpers -{ - public static class MemberInfoExtensions - { - public static bool IsPublicOrInternal(this MethodInfo method) - { - if (method == null) throw new ArgumentNullException(nameof(method)); - - return !method.IsPrivate - && !method.IsFamily - && !method.IsFamilyOrAssembly - && !method.IsFamilyAndAssembly - && (method.IsPublic || true); - } - - - - public static bool IsGetterPublicOrInternal(this PropertyInfo property) - { - if (property == null) throw new ArgumentNullException(nameof(property)); - - MethodInfo? getMethod = property.GetMethod; - - if (getMethod == null) return false; - - return getMethod.IsPublicOrInternal(); - } - } - -} diff --git a/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs b/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs deleted file mode 100644 index d02cc84e..00000000 --- a/src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; - -namespace ExpressionDebugger.Helpers -{ - public static class RandomNamespaceGenerator - { - public static readonly Regex CheckNameSpace = new Regex(@"^([a-zA-Z_]\w*)(\.[a-zA-Z_]\w*)*$", RegexOptions.Compiled | RegexOptions.CultureInvariant); - private const string Consonants = "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ"; - private const string Vowels = "aeiouAEIOU"; - private const string Digits = "0123456789"; - - public static string Generate(string input, int minParts = 2, int maxParts = 4) - { - if (string.IsNullOrEmpty(input)) throw new ArgumentException("Input cannot be empty."); - if (minParts < 1) minParts = 1; - if (maxParts < minParts) maxParts = minParts; - - using var sha256 = SHA256.Create(); - byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); - - long seed = BitConverter.ToInt64(hashBytes, 0); - var random = new Random(unchecked((int)seed ^ (int)(seed >> 32))); - - int partsCount = random.Next(minParts, maxParts + 1); - var sb = new StringBuilder(); - - for (int i = 0; i < partsCount; i++) - { - if (i > 0) sb.Append('.'); - sb.Append(GeneratePart(random)); - } - - return sb.ToString(); - } - - - public static string Generate(int minParts = 2, int maxParts = 4) - { - if (minParts < 1) minParts = 1; - if (maxParts < minParts) maxParts = minParts; - - var _random = new Random(); - - int partsCount = _random.Next(minParts, maxParts + 1); - var sb = new StringBuilder(); - - for (int i = 0; i < partsCount; i++) - { - if (i > 0) sb.Append('.'); - sb.Append(GeneratePart(_random)); - } - - return sb.ToString(); - } - - private static string GeneratePart(Random random, int minLength = 2, int maxLength = 10) - { - if (minLength < 1) minLength = 1; - if (maxLength < minLength) maxLength = minLength; - - int length = random.Next(minLength, maxLength + 1); - var sb = new StringBuilder(length); - - sb.Append(Consonants[random.Next(Consonants.Length)]); - - for (int i = 1; i < length; i++) - { - string pool = (i % 2 == 0) ? Vowels : Consonants; - if (random.NextDouble() < 0.1) pool = Digits; - sb.Append(pool[random.Next(pool.Length)]); - } - - return sb.ToString(); - } - } -} - diff --git a/src/ExpressionTranslator/TypeDefinitions.cs b/src/ExpressionTranslator/TypeDefinitions.cs index ec730304..a8e20667 100644 --- a/src/ExpressionTranslator/TypeDefinitions.cs +++ b/src/ExpressionTranslator/TypeDefinitions.cs @@ -1,5 +1,4 @@ -using ExpressionDebugger.Helpers.GeneratedAttributes; -using System; +using System; using System.Collections.Generic; namespace ExpressionDebugger @@ -13,7 +12,6 @@ public class TypeDefinitions public IEnumerable? Implements { get; set; } public bool PrintFullTypeName { get; set; } public bool IsRecordType { get; set; } - public HashSet GeneratedAttributes { get; set; } = new HashSet(); /// /// Set to 2 to mark all properties as nullable diff --git a/src/Mapster.Tool/MapperOptions.cs b/src/Mapster.Tool/MapperOptions.cs index ce0b6fc5..9cd8a1aa 100644 --- a/src/Mapster.Tool/MapperOptions.cs +++ b/src/Mapster.Tool/MapperOptions.cs @@ -28,13 +28,6 @@ public class MapperOptions [Option('N', "nullableDirective", Required = false, HelpText = "Set true to add \"#nullable enable\" to the top of generated mapper files")] public bool GenerateNullableDirective { get; set; } - [Option('h', "helpersCreate", Required = false, HelpText = "Generate helpers features")] - public bool CreateHelpers { get; set; } - - [Option('H', "helpersNamespace", Required = false, HelpText = "Specify additional namespace to generated helpers features")] - public string? HelpersNamespace { get; set; } - - [Usage(ApplicationAlias = "dotnet mapster mapper")] public static IEnumerable Examples => new List diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index e93ce4d4..1347cb13 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -1,10 +1,4 @@ -using CommandLine; -using ExpressionDebugger; -using ExpressionDebugger.Helpers; -using ExpressionDebugger.Helpers.GeneratedAttributes; -using Mapster.Models; -using Mapster.Utils; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,6 +6,10 @@ using System.Reflection; using System.Runtime.Loader; using System.Text; +using CommandLine; +using ExpressionDebugger; +using Mapster.Models; +using Mapster.Utils; namespace Mapster.Tool { @@ -93,14 +91,6 @@ private static void GenerateMappers(MapperOptions opt) config.SelfContainedCodeGeneration = true; config.Scan(assembly); - var generatedAtrr = new List(); - - if (opt.CreateHelpers) - generatedAtrr.Add(new MapsterToolGeneratedMapperAttribute( - opt.HelpersNamespace ?? Path.GetFileNameWithoutExtension(opt.Assembly) - )); - - foreach (var type in assembly.GetLoadableTypes()) { if (!type.IsInterface) @@ -119,11 +109,8 @@ private static void GenerateMappers(MapperOptions opt) TypeName = attr.Name ?? GetImplName(GetCodeFriendlyTypeName(type)), IsInternal = attr.IsInternal, PrintFullTypeName = opt.PrintFullTypeName, - GeneratedAttributes = new(generatedAtrr) }; - bool? _isForceInternal = definitions.IsInternal ? true : null; - var path = GetOutput(opt.Output, segments, definitions.TypeName); if (opt.SkipExistingFiles && File.Exists(path)) { @@ -137,9 +124,7 @@ private static void GenerateMappers(MapperOptions opt) var interfaces = type.GetAllInterfaces(); foreach (var @interface in interfaces) { - foreach (var prop in @interface.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(x => x.IsGetterPublicOrInternal()) - ) + foreach (var prop in @interface.GetProperties()) { if (!prop.PropertyType.IsGenericType) continue; @@ -153,21 +138,17 @@ private static void GenerateMappers(MapperOptions opt) var funcArgs = propArgs.GetGenericArguments(); var tuple = new TypeTuple(funcArgs[0], funcArgs[1]); var expr = config.CreateMapExpression(tuple, MapType.Projection); - translator.VisitLambdaForGenerateMappers( + translator.VisitLambda( expr, ExpressionTranslator.LambdaType.PublicLambda, - @interface, - prop.Name, - _isForceInternal ?? (!prop.GetMethod?.IsPublic ?? false) + prop.Name ); } } foreach (var @interface in interfaces) { - foreach (var method in @interface.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(x => x.IsPublicOrInternal()) - ) + foreach (var method in @interface.GetMethods()) { if (method.IsGenericMethod) continue; @@ -181,12 +162,10 @@ private static void GenerateMappers(MapperOptions opt) tuple, methodArgs.Length == 1 ? MapType.Map : MapType.MapToTarget ); - translator.VisitLambdaForGenerateMappers( + translator.VisitLambda( expr, ExpressionTranslator.LambdaType.PublicMethod, - @interface, - method.Name, - _isForceInternal ?? !method.IsPublic + method.Name ); } } @@ -196,12 +175,6 @@ private static void GenerateMappers(MapperOptions opt) : translator.ToString(); WriteFile(code, path); } - - - foreach (var item in generatedAtrr) - { - WriteFile(item.Declaration, GetOutput(opt.Output, null, item.FileName)); - } } private static string GetImplName(string name) diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs index d71ab9d7..4929bb12 100644 --- a/src/TemplateTest/CreateMapExpressionTest.cs +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -1,14 +1,7 @@ using ExpressionDebugger; -using ExpressionDebugger.Helpers; -using ExpressionDebugger.Helpers.GeneratedAttributes; using Mapster; -using Mapster.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; namespace TemplateTest { @@ -71,107 +64,6 @@ public void TestCreateProjectionExpression() Assert.IsNotNull(code); } - - /// - /// https://github.com/MapsterMapper/Mapster/issues/399 - /// - [TestMethod] - public void TestRegressionMapperGenerationTranslation() - { - var S = new MapsterToolGeneratedMapperAttribute("Test"); - - var config = new TypeAdapterConfig(); - config.SelfContainedCodeGeneration = true; - - var definitions = new TypeDefinitions - { - Implements = new[] { typeof(IMyTypeMapper), typeof(IMyTypeMapperIntenal) }, - Namespace = "Benchmark", - TypeName = "CustomerMapper", - IsInternal = false, - GeneratedAttributes = new(new[] {new MapsterToolGeneratedMapperAttribute("Test") }) - }; - - var translator = new ExpressionTranslator(definitions); - - translator.CreateFromInterface(definitions, config); - - var code = translator.ToString(); - - Assert.IsTrue(code.Contains("public partial class CustomerMapper")); // mapper class is public - Assert.IsTrue(code.Contains("AddressDTO TemplateTest.IMyTypeMapper.Map")); - Assert.IsTrue(code.Contains("[MapsterToolGeneratedMapper]")); - - Assert.IsTrue(code.Contains("internal AddressDTO Map")); // create internal method in public interface - - // create as internal because declarate in internal interface and using internal type AddressInternal - Assert.IsTrue(code.Contains("internal AddressInternal MapInternal")); - Assert.IsTrue(code.Contains("internal Expression> ProjectionInternal")); - - - Assert.IsTrue(code.Contains("public AddressDTO MapPublicClassInInternalInterface")); // create public method in internal interface because using public types - - // method using public types in internal interface but marked as internal create as internal method - Assert.IsTrue(code.Contains("internal AddressDTO MapPublicClassInInternalInterfaceWithMarkInternal")); - } - - [TestMethod] - public void CreateForceInternalMapper() - { - var config = new TypeAdapterConfig(); - config.SelfContainedCodeGeneration = true; - - var definitions = new TypeDefinitions - { - Implements = new[] { typeof(IMyTypeMapperForce)}, - Namespace = "Benchmark", - TypeName = "CustomerMapper", - IsInternal = true, // force create internal mapper - GeneratedAttributes = new(new[] { new MapsterToolGeneratedMapperAttribute("Test") }) - }; - - var translator = new ExpressionTranslator(definitions); - - translator.CreateFromInterface(definitions, config); - - var code = translator.ToString(); - - Assert.IsTrue(code.Contains("internal partial class CustomerMapper")); // mapper class is internal - - // force create internal method using only public types because mapper class is internal - Assert.IsTrue(code.Contains("internal AddressDTO Map")); - } - - - - } - - - public interface IMyTypeMapper - { - internal AddressDTO Map(Address p1); - public Expression> Projection { get; } - } - - internal interface IMyTypeMapperIntenal - { - AddressInternal MapInternal(Address p1); - Expression> ProjectionInternal { get; } - AddressDTO MapPublicClassInInternalInterface(Address p1); - internal AddressDTO MapPublicClassInInternalInterfaceWithMarkInternal(Address p1); - } - - public interface IMyTypeMapperForce - { - AddressDTO Map(Address p1); - } - - internal class AddressInternal - { - public int Id { get; set; } - public string Street { get; set; } - public string City { get; set; } - public string Country { get; set; } } public class Address @@ -210,68 +102,4 @@ public class CustomerDTO public List WorkAddresses { get; set; } public string AddressCity { get; set; } } - - static class GenerateMappersExtensions - { - public static void CreateFromInterface(this ExpressionTranslator translator, TypeDefinitions definitions, TypeAdapterConfig config) - { - if (definitions.Implements == null) - return; - - foreach (var interfaceType in definitions.Implements) - { - bool? _isForceInternal = definitions.IsInternal ? true : null; - - foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(x => x.IsPublicOrInternal()) - ) - { - if (method.IsGenericMethod) - continue; - if (method.ReturnType == typeof(void)) - continue; - var methodArgs = method.GetParameters(); - if (methodArgs.Length < 1 || methodArgs.Length > 2) - continue; - var tuple = new TypeTuple(methodArgs[0].ParameterType, method.ReturnType); - var expr = config.CreateMapExpression( - tuple, - methodArgs.Length == 1 ? MapType.Map : MapType.MapToTarget - ); - translator.VisitLambdaForGenerateMappers( - expr, - ExpressionTranslator.LambdaType.PublicMethod, - interfaceType, - method.Name, - _isForceInternal ?? !method.IsPublic - ); - } - - foreach (var prop in interfaceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(x => x.IsGetterPublicOrInternal()) - ) - { - if (!prop.PropertyType.IsGenericType) - continue; - if (prop.PropertyType.GetGenericTypeDefinition() != typeof(Expression<>)) - continue; - var propArgs = prop.PropertyType.GetGenericArguments()[0]; - if (!propArgs.IsGenericType) - continue; - if (propArgs.GetGenericTypeDefinition() != typeof(Func<,>)) - continue; - var funcArgs = propArgs.GetGenericArguments(); - var tuple = new TypeTuple(funcArgs[0], funcArgs[1]); - var expr = config.CreateMapExpression(tuple, MapType.Projection); - translator.VisitLambdaForGenerateMappers( - expr, - ExpressionTranslator.LambdaType.PublicLambda, - interfaceType, - prop.Name, - _isForceInternal ?? (!prop.GetMethod?.IsPublic ?? false) - ); - } - } - } - } } \ No newline at end of file