From c8565df19c0f1f753b240acfc04835bf833a6f31 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 23 May 2025 18:12:13 +0500 Subject: [PATCH 1/5] add custom Default --- .../WhenNullPropagationRegression.cs | 91 +++++++++++++++++++ src/Mapster/Adapters/BaseClassAdapter.cs | 6 +- src/Mapster/Models/InvokerModel.cs | 1 + src/Mapster/TypeAdapterSetter.cs | 5 +- src/Mapster/Utils/ExpressionEx.cs | 27 +++++- 5 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/Mapster.Tests/WhenNullPropagationRegression.cs diff --git a/src/Mapster.Tests/WhenNullPropagationRegression.cs b/src/Mapster.Tests/WhenNullPropagationRegression.cs new file mode 100644 index 00000000..ad863f57 --- /dev/null +++ b/src/Mapster.Tests/WhenNullPropagationRegression.cs @@ -0,0 +1,91 @@ +using Mapster.Tests.Classes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using System; + +namespace Mapster.Tests; + +[TestClass] +public class WhenNullPropagationRegression +{ + [TestMethod] + public void WhenCustomDefaultWorked() + { + string customdefault = "42"; + + TypeAdapterConfig.NewConfig() + .Map(dest => dest.AnotherName, src => src.Name, defaultValue:customdefault) + .Map(dest => dest.LastModified, src => DateTime.Now) + .Compile(); + + var poco = new SimplePocoToNullPropagation { Id = Guid.NewGuid(), Name = null}; + + var dto = poco.Adapt(); + + dto.Id.ShouldBe(poco.Id); + dto.Name.ShouldBe(null); + dto.AnotherName.ShouldBe(customdefault); + } + + [TestMethod] + public void WhenCustomDefaultMapPathWorked() + { + string customdefault = "Default Location"; + + TypeAdapterConfig.NewConfig() + .Map(dest => dest.Location, src => src.Child.Address.Location, defaultValue:customdefault); + + var poco = new PocoToPathNullPropagation + { + Id = Guid.NewGuid(), + Child = new() + }; + + var dto = poco.Adapt(); + dto.Id.ShouldBe(poco.Id); + dto.Location.ShouldBe(customdefault); + } + + #region Classes + + public class SimplePocoToNullPropagation + { + public Guid Id { get; set; } + public string? Name { get; set; } + } + + public class SimpleDtoToNullPropagation + { + public Guid Id { get; set; } + public string Name { get; set; } + + public string AnotherName { get; set; } + public DateTime LastModified { get; set; } + + } + + public class PocoToPathNullPropagation + { + public Guid Id { get; set; } + public ChildPocoToNullPropagation? Child { get; set; } + } + + public class DtoToPathNullPropagation + { + public Guid Id { get; set; } + public Address Address { get; set; } + public string Location { get; set; } + } + + public class ChildPocoToNullPropagation + { + public AddressNullPropagation? Address { get; set; } + } + public class AddressNullPropagation + { + public string? Number { get; set; } + public string? Location { get; set; } + } + + #endregion Classes +} diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index faa490ec..c03013c0 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -56,9 +56,13 @@ select fn(src, destinationMember, arg)) }; if (getter != null) { + var customDefault = arg.Settings.Resolvers + .Where(x=>x.DefaultValue != null && x.DestinationMemberName == destinationMember.Name) + .FirstOrDefault()?.DefaultValue; + propertyModel.Getter = arg.MapType == MapType.Projection ? getter - : getter.ApplyNullPropagation(); + : getter.ApplyNullPropagation(customDefault); properties.Add(propertyModel); } else diff --git a/src/Mapster/Models/InvokerModel.cs b/src/Mapster/Models/InvokerModel.cs index b5a4546d..8608483b 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 LambdaExpression? DefaultValue { get; set; } public bool IsChildPath { get; set; } public InvokerModel? Next(ParameterExpression source, string destMemberName) diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index a0581845..9c73c008 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -552,7 +552,9 @@ public TypeAdapterSetter IgnoreIf( public TypeAdapterSetter Map( Expression> member, - Expression> source, Expression>? shouldMap = null) + Expression> source, + Expression>? shouldMap = null, + TDestinationMember defaultValue = default) { this.CheckCompiled(); @@ -568,6 +570,7 @@ public TypeAdapterSetter Map).MakeGenericType(defValue.Type)); + if(customDefaultValue == null) + defValue = defValue.Type.CreateDefault(); + } if (expr.CanBeNull()) { var compareNull = Expression.Equal(expr, Expression.Constant(null, expr.Type)); + + if (memberNestingLevel == 0 && customDefaultValue != null && getter.Type.CanBeNull()) + compareNull = Expression.OrElse(compareNull, Expression.Equal(getter, Expression.Constant(null, getter.Type))); + if (!result.Type.CanBeNull()) result = Expression.Convert(result, typeof(Nullable<>).MakeGenericType(result.Type)); - result = Expression.Condition(compareNull, result.Type.CreateDefault(), result); + result = Expression.Condition(compareNull, defValue, result); } + memberNestingLevel++; current = expr; } + if (current.NodeType == ExpressionType.Parameter && customDefaultValue != null) + return result; + return getter; } From d38d96d8ea6f394905fd82df1981910ebaa061a5 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 26 May 2025 17:14:21 +0500 Subject: [PATCH 2/5] add ThrowWhenNull --- .../WhenNullPropagationRegression.cs | 18 ++ src/Mapster/Adapters/BaseAdapter.cs | 2 + src/Mapster/Adapters/BaseClassAdapter.cs | 31 ++- src/Mapster/Adapters/ClassAdapter.cs | 13 +- src/Mapster/Models/InvokerModel.cs | 1 + src/Mapster/TypeAdapterSetter.cs | 255 ++++++++++++------ src/Mapster/Utils/ExpressionEx.cs | 57 ++++ 7 files changed, 276 insertions(+), 101 deletions(-) diff --git a/src/Mapster.Tests/WhenNullPropagationRegression.cs b/src/Mapster.Tests/WhenNullPropagationRegression.cs index ad863f57..76e5c001 100644 --- a/src/Mapster.Tests/WhenNullPropagationRegression.cs +++ b/src/Mapster.Tests/WhenNullPropagationRegression.cs @@ -46,6 +46,24 @@ public void WhenCustomDefaultMapPathWorked() dto.Location.ShouldBe(customdefault); } + [TestMethod] + public void WhenThrowWhenNullWorked() + { + TypeAdapterConfig.NewConfig() + .Map(dest => dest.Location, src => src.Child.Address.Location).ThrowWhenNull(); + + var poco = new PocoToPathNullPropagation + { + Id = Guid.NewGuid(), + Child = new() + }; + + Should.Throw(() => + { + var dto = poco.Adapt(); + }); + } + #region Classes public class SimplePocoToNullPropagation diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 366f1eeb..a3e8e3f9 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -60,6 +60,8 @@ protected virtual void DecorateRule(TypeAdapterRule rule) { } protected virtual bool CanInline(Expression source, Expression? destination, CompileArgument arg) { + if (arg.Settings.Resolvers.Any(x=>x.IsThrowWhenNull)) + return false; if (arg.MapType == MapType.Projection) return true; diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index c03013c0..ece43d98 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -54,17 +54,26 @@ select fn(src, destinationMember, arg)) Destination = (ParameterExpression?)destination, UseDestinationValue = arg.MapType != MapType.Projection && destinationMember.UseDestinationValue(arg), }; - if (getter != null) - { - var customDefault = arg.Settings.Resolvers - .Where(x=>x.DefaultValue != null && x.DestinationMemberName == destinationMember.Name) - .FirstOrDefault()?.DefaultValue; - - propertyModel.Getter = arg.MapType == MapType.Projection - ? getter - : getter.ApplyNullPropagation(customDefault); - properties.Add(propertyModel); - } + if (getter != null) + { + var customDefault = arg.Settings.Resolvers + .Where(x=>x.DefaultValue != null && x.DestinationMemberName == destinationMember.Name) + .FirstOrDefault()?.DefaultValue; + + var whenNullisThrow = arg.Settings.Resolvers + .Where(x => x.IsThrowWhenNull && x.DestinationMemberName == destinationMember.Name) + .FirstOrDefault()?.IsThrowWhenNull; + + if (whenNullisThrow == true) + propertyModel.Getter = getter; + else + { + propertyModel.Getter = arg.MapType == MapType.Projection + ? getter + : getter.ApplyNullPropagation(customDefault); + } + properties.Add(propertyModel); + } else { if (arg.Settings.IgnoreNonMapped != true && diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 407c5a5b..07428a2f 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -161,7 +161,18 @@ protected override Expression CreateBlockExpression(Expression source, Expressio tuple.Item1.Add(adapt); } else - lines.Add(adapt); + { + var whenNullisThrow = arg.Settings.Resolvers + .Where(x => x.IsThrowWhenNull && x.DestinationMemberName == member.DestinationMember.Name) + .FirstOrDefault()?.IsThrowWhenNull; + + if (whenNullisThrow == true) + { + lines.Add(adapt.ApplyThrowPropagation()); + } + else + lines.Add(adapt); + } } if (conditions != null) diff --git a/src/Mapster/Models/InvokerModel.cs b/src/Mapster/Models/InvokerModel.cs index 8608483b..d3800dfd 100644 --- a/src/Mapster/Models/InvokerModel.cs +++ b/src/Mapster/Models/InvokerModel.cs @@ -10,6 +10,7 @@ public class InvokerModel public string? SourceMemberName { get; set; } public LambdaExpression? Condition { get; set; } public LambdaExpression? DefaultValue { get; set; } + public bool IsThrowWhenNull { get; set; } public bool IsChildPath { get; set; } public InvokerModel? Next(ParameterExpression source, string destMemberName) diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 9c73c008..a76db09f 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -337,49 +337,7 @@ public TypeAdapterSetter Ignore(params Expression Map( - Expression> member, - Expression> source) - { - this.CheckCompiled(); - - var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof (object))); - if (member.IsIdentity()) - { - Settings.ExtraSources.Add(invoker); - return this; - } - - Settings.Resolvers.Add(new InvokerModel - { - DestinationMemberName = member.GetMemberPath()!, - Invoker = invoker, - Condition = null - }); - return this; - } - - public TypeAdapterSetter Map( - Expression> destinationMember, - string sourceMemberName) - { - this.CheckCompiled(); - - if (destinationMember.IsIdentity()) - { - Settings.ExtraSources.Add(sourceMemberName); - return this; - } - - Settings.Resolvers.Add(new InvokerModel - { - DestinationMemberName = destinationMember.GetMemberPath()!, - SourceMemberName = sourceMemberName, - Condition = null - }); - - return this; - } + public TypeAdapterSetter ConstructUsing(Expression> constructUsing) { @@ -472,23 +430,50 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren #region replace for chaining + public MapTypeAdapterSetter Map( + Expression> member, + Expression> source, + Expression>? shouldMap = null, + TDestinationMember defaultValue = default) + { + this.CheckCompiled(); + + var result = new MapTypeAdapterSetter(this.Settings,this.Config).Map(member, source, shouldMap, defaultValue); + return result; + } + + public MapTypeAdapterSetter Map( + string memberName, + Expression> source, Expression>? shouldMap = null) + { + this.CheckCompiled(); + + var result = new MapTypeAdapterSetter(this.Settings, this.Config).Map(memberName, source, shouldMap); + return result; + } + public new TypeAdapterSetter Ignore(params Expression>[] members) { return (TypeAdapterSetter)base.Ignore(members); } - public new TypeAdapterSetter Map( + public new MapTypeAdapterSetter Map( Expression> member, Expression> source) { - return (TypeAdapterSetter)base.Map(member, source); + + var result = new MapTypeAdapterSetter(this.Settings,this.Config).Map(member, source); + + return result; } - public new TypeAdapterSetter Map( + public new MapTypeAdapterSetter Map( Expression> destinationMember, string sourceMemberName) { - return (TypeAdapterSetter)base.Map(destinationMember, sourceMemberName); + + var result = new MapTypeAdapterSetter(this.Settings, this.Config).Map(destinationMember, sourceMemberName); + return result; } public new TypeAdapterSetter ConstructUsing(Expression> constructUsing) @@ -550,48 +535,7 @@ public TypeAdapterSetter IgnoreIf( return this; } - public TypeAdapterSetter Map( - Expression> member, - Expression> source, - Expression>? shouldMap = null, - TDestinationMember defaultValue = default) - { - this.CheckCompiled(); - - var sourceName = source.GetMemberPath(noError: true); - if (member.IsIdentity()) - { - Settings.ExtraSources.Add((object?)sourceName ?? source); - return this; - } - - Settings.Resolvers.Add(new InvokerModel - { - DestinationMemberName = member.GetMemberPath()!, - SourceMemberName = sourceName, - Invoker = source, - DefaultValue = defaultValue == null ? null : Expression.Lambda(Expression.Constant( defaultValue)), - Condition = shouldMap - }); - return this; - } - - public TypeAdapterSetter Map( - string memberName, - Expression> source, Expression>? shouldMap = null) - { - this.CheckCompiled(); - - Settings.Resolvers.Add(new InvokerModel - { - DestinationMemberName = memberName, - SourceMemberName = source.GetMemberPath(noError: true), - Invoker = source, - Condition = shouldMap - }); - - return this; - } + public TypeAdapterSetter ConstructUsing(Expression> constructUsing) { @@ -1093,4 +1037,137 @@ public TwoWaysTypeAdapterSetter GenerateMapper(MapType ma return this; } } + + public class MapTypeAdapterSetter : TypeAdapterSetter, IMapTypeAdapterSetter + { + public MapTypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig parentConfig) + : base(settings, parentConfig) + { } + + public string TSourceMemberName { get; set; } + public string TDestinationMemberName { get; set; } + + public MapTypeAdapterSetter Map( + Expression> member, + Expression> source) + { + this.CheckCompiled(); + + this.TSourceMemberName = source.GetMemberPath()!; + this.TDestinationMemberName = member.GetMemberPath()!; + + var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof (object))); + if (member.IsIdentity()) + { + Settings.ExtraSources.Add(invoker); + return this; + } + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = member.GetMemberPath(noError: true)!, + Invoker = invoker, + Condition = null + }); + return this; + } + + public MapTypeAdapterSetter Map( + Expression> destinationMember, + string sourceMemberName) + { + this.CheckCompiled(); + + this.TSourceMemberName = sourceMemberName; + this.TDestinationMemberName = destinationMember.GetMemberPath(noError: true)!; + + if (destinationMember.IsIdentity()) + { + Settings.ExtraSources.Add(sourceMemberName); + return this; + } + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = destinationMember.GetMemberPath(noError: true)!, + SourceMemberName = sourceMemberName, + Condition = null + }); + + return this; + } + + public MapTypeAdapterSetter Map( + Expression> member, + Expression> source, + Expression>? shouldMap = null, + TDestinationMember defaultValue = default) + { + this.CheckCompiled(); + + this.TSourceMemberName = source.GetMemberPath(noError: true)!; + this.TDestinationMemberName = member.GetMemberPath(noError: true)!; + + var sourceName = source.GetMemberPath(noError: true); + if (member.IsIdentity()) + { + Settings.ExtraSources.Add((object?)sourceName ?? source); + return this; + } + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = member.GetMemberPath(noError: true)!, + SourceMemberName = sourceName, + Invoker = source, + DefaultValue = defaultValue == null ? null : Expression.Lambda(Expression.Constant(defaultValue)), + Condition = shouldMap + }); + return this; + } + + public MapTypeAdapterSetter Map( + string memberName, + Expression> source, Expression>? shouldMap = null) + { + this.CheckCompiled(); + + this.TSourceMemberName = source.GetMemberPath(noError: true)!; + this.TDestinationMemberName = memberName; + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = memberName, + SourceMemberName = source.GetMemberPath(noError: true), + Invoker = source, + Condition = shouldMap + }); + + return this; + } + + } + + public interface IMapTypeAdapterSetter + { + string TSourceMemberName { get; set; } + string TDestinationMemberName { get; set; } + } + + public static class SetterExtention + { + public static TSetter ThrowWhenNull(this TSetter setter) where TSetter : TypeAdapterSetter, IMapTypeAdapterSetter + { + setter.CheckCompiled(); + + var find = setter.Settings.Resolvers + .Where(x => x.SourceMemberName == setter.TSourceMemberName && x.DestinationMemberName == setter.TDestinationMemberName) + .FirstOrDefault(); + + if (find != null) + find.IsThrowWhenNull = true; + + return setter; + } + } } diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 076e85c0..a699c368 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -371,6 +371,63 @@ public static Expression ApplyNullPropagation(this Expression getter, LambdaExpr return getter; } + public static Expression ApplyThrowPropagation(this Expression getter) + { + var types = new Type[1]; + types[0] =(typeof(string)); + var constructorInfo = typeof(NullReferenceException).GetConstructor(types); + + if (getter is BinaryExpression get) + { + var resultType = ((MemberExpression)get.Left).Expression?.Type.Name; + var sourceType = ((MemberExpression)get.Right).Expression?.Type.Name; + var memberNestingLevel = 0; + + var current = get.Right; + var result = getter; + while (current.NodeType == ExpressionType.MemberAccess) + { + var memEx = (MemberExpression)current; + var expr = memEx.Expression; + if (expr == null) + break; + if (expr.NodeType == ExpressionType.Parameter) + return result; + + if (expr.CanBeNull()) + { + var compareNull = Expression.Equal(expr, Expression.Constant(null, expr.Type)); + + var argumets = new Expression[1]; + var membername = (expr as MemberExpression).Member.Name; + + if (memberNestingLevel == 0 && current.Type.CanBeNull()) + { + compareNull = Expression.OrElse(compareNull, Expression.Equal(current, Expression.Constant(null, current.Type))); + + var memberPath = Expression.Lambda(memEx).GetMemberPath(noError: true); + argumets[0] = Expression.Constant($"Member: {membername} or {memEx.Member.Name} by path: {memberPath} was null!; Mapping Types: {sourceType} to {resultType}"); + + } + else + { + var memberPath = Expression.Lambda(expr).GetMemberPath(noError: true); + argumets[0] = Expression.Constant($"Member: {membername} by path: {memberPath} was null!; Mapping Types: {sourceType} to {resultType}"); + } + + result = Expression.IfThenElse(compareNull, Expression + .Throw(Expression.New(constructorInfo, argumets)), result); + } + + current = expr; + memberNestingLevel++; + } + + } + return getter; + } + + public static string? GetMemberPath(this LambdaExpression lambda, bool firstLevelOnly = false, bool noError = false) { var props = new List(); From 6cebaceaf765d709c24f004db84e68c6f30f9499 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 28 May 2025 10:42:05 +0500 Subject: [PATCH 3/5] refactoring SourceDefaultValue --- .../WhenNullPropagationRegression.cs | 4 +- src/Mapster/TypeAdapterSetter.cs | 98 +++++++++++++------ 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/src/Mapster.Tests/WhenNullPropagationRegression.cs b/src/Mapster.Tests/WhenNullPropagationRegression.cs index 76e5c001..a0e3e666 100644 --- a/src/Mapster.Tests/WhenNullPropagationRegression.cs +++ b/src/Mapster.Tests/WhenNullPropagationRegression.cs @@ -14,7 +14,7 @@ public void WhenCustomDefaultWorked() string customdefault = "42"; TypeAdapterConfig.NewConfig() - .Map(dest => dest.AnotherName, src => src.Name, defaultValue:customdefault) + .Map(dest => dest.AnotherName, src => src.Name).SourceDefaultValue(customdefault) .Map(dest => dest.LastModified, src => DateTime.Now) .Compile(); @@ -33,7 +33,7 @@ public void WhenCustomDefaultMapPathWorked() string customdefault = "Default Location"; TypeAdapterConfig.NewConfig() - .Map(dest => dest.Location, src => src.Child.Address.Location, defaultValue:customdefault); + .Map(dest => dest.Location, src => src.Child.Address.Location).SourceDefaultValue(customdefault); var poco = new PocoToPathNullPropagation { diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index a76db09f..fc84fbb9 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -430,15 +430,14 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren #region replace for chaining - public MapTypeAdapterSetter Map( + public MapTypeAdapterSetterAllMemberTypes Map( Expression> member, Expression> source, - Expression>? shouldMap = null, - TDestinationMember defaultValue = default) + Expression>? shouldMap = null) { this.CheckCompiled(); - var result = new MapTypeAdapterSetter(this.Settings,this.Config).Map(member, source, shouldMap, defaultValue); + var result = new MapTypeAdapterSetterAllMemberTypes(this.Settings,this.Config).Map(member, source, shouldMap); return result; } @@ -1047,7 +1046,7 @@ public MapTypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig pare public string TSourceMemberName { get; set; } public string TDestinationMemberName { get; set; } - public MapTypeAdapterSetter Map( + public new MapTypeAdapterSetter Map( Expression> member, Expression> source) { @@ -1072,7 +1071,7 @@ public MapTypeAdapterSetter Map Map( + public new MapTypeAdapterSetter Map( Expression> destinationMember, string sourceMemberName) { @@ -1097,55 +1096,77 @@ public MapTypeAdapterSetter Map( return this; } - public MapTypeAdapterSetter Map( - Expression> member, - Expression> source, - Expression>? shouldMap = null, - TDestinationMember defaultValue = default) + + + public new MapTypeAdapterSetter Map( + string memberName, + Expression> source, Expression>? shouldMap = null) { this.CheckCompiled(); this.TSourceMemberName = source.GetMemberPath(noError: true)!; - this.TDestinationMemberName = member.GetMemberPath(noError: true)!; - - var sourceName = source.GetMemberPath(noError: true); - if (member.IsIdentity()) - { - Settings.ExtraSources.Add((object?)sourceName ?? source); - return this; - } + this.TDestinationMemberName = memberName; Settings.Resolvers.Add(new InvokerModel { - DestinationMemberName = member.GetMemberPath(noError: true)!, - SourceMemberName = sourceName, + DestinationMemberName = memberName, + SourceMemberName = source.GetMemberPath(noError: true), Invoker = source, - DefaultValue = defaultValue == null ? null : Expression.Lambda(Expression.Constant(defaultValue)), Condition = shouldMap }); + return this; } - public MapTypeAdapterSetter Map( - string memberName, - Expression> source, Expression>? shouldMap = null) + + + } + + public class MapTypeAdapterSetterAllMemberTypes : MapTypeAdapterSetter, IMapTypeAdapterAllMembertypes + { + public MapTypeAdapterSetterAllMemberTypes(TypeAdapterSettings settings, TypeAdapterConfig parentConfig) + : base(settings, parentConfig) + { } + + public Type TSourceMember { get; set; } + public Type TDestinationMember { get; set; } + public string TSourceMemberName { get; set; } + public string TDestinationMemberName { get; set; } + + public new MapTypeAdapterSetterAllMemberTypes Map( + Expression> member, + Expression> source, + Expression>? shouldMap = null) { this.CheckCompiled(); this.TSourceMemberName = source.GetMemberPath(noError: true)!; - this.TDestinationMemberName = memberName; + this.TDestinationMemberName = member.GetMemberPath(noError: true)!; + this.TSourceMember = source.ReturnType; + this.TDestinationMember = source.ReturnType; + + var sourceName = source.GetMemberPath(noError: true); + if (member.IsIdentity()) + { + Settings.ExtraSources.Add((object?)sourceName ?? source); + return this; + } Settings.Resolvers.Add(new InvokerModel { - DestinationMemberName = memberName, - SourceMemberName = source.GetMemberPath(noError: true), + DestinationMemberName = member.GetMemberPath(noError: true)!, + SourceMemberName = sourceName, Invoker = source, Condition = shouldMap }); - return this; } + } + public interface IMapTypeAdapterAllMembertypes : IMapTypeAdapterSetter + { + Type TSourceMember { get; set; } + Type TDestinationMember { get; set; } } public interface IMapTypeAdapterSetter @@ -1169,5 +1190,24 @@ public static TSetter ThrowWhenNull(this TSetter setter) where TSetter return setter; } + + public static TSetter SourceDefaultValue(this TSetter setter, TSourceMember defaultValue) where TSetter : TypeAdapterSetter, IMapTypeAdapterAllMembertypes + { + setter.CheckCompiled(); + + if (typeof(TSourceMember) != setter.TSourceMember) + throw new ArgumentException($"Type of DefaultValue is {typeof(TSourceMember).Name}, but must be {setter.TSourceMember.Name}"); + + var find = setter.Settings.Resolvers + .Where(x => x.SourceMemberName == setter.TSourceMemberName && x.DestinationMemberName == setter.TDestinationMemberName) + .FirstOrDefault(); + + if (find != null) + find.DefaultValue = Expression.Lambda(Expression.Constant(defaultValue)); + + + return setter; + } + } } From b96810aae70fb072230f3da77c9c26555f41b5fb Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 29 May 2025 09:27:57 +0500 Subject: [PATCH 4/5] fix ThrowWhenNull --- src/Mapster/Utils/ExpressionEx.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index a699c368..f3d2294b 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -377,6 +377,8 @@ public static Expression ApplyThrowPropagation(this Expression getter) types[0] =(typeof(string)); var constructorInfo = typeof(NullReferenceException).GetConstructor(types); + string labelSource = "{Source}"; + if (getter is BinaryExpression get) { var resultType = ((MemberExpression)get.Left).Expression?.Type.Name; @@ -391,28 +393,29 @@ public static Expression ApplyThrowPropagation(this Expression getter) var expr = memEx.Expression; if (expr == null) break; - if (expr.NodeType == ExpressionType.Parameter) - return result; - + if (expr.CanBeNull()) { var compareNull = Expression.Equal(expr, Expression.Constant(null, expr.Type)); var argumets = new Expression[1]; - var membername = (expr as MemberExpression).Member.Name; + string membername; + if (expr.NodeType == ExpressionType.Parameter) + membername = "Source"; + else + membername = (expr as MemberExpression).Member.Name; if (memberNestingLevel == 0 && current.Type.CanBeNull()) { compareNull = Expression.OrElse(compareNull, Expression.Equal(current, Expression.Constant(null, current.Type))); var memberPath = Expression.Lambda(memEx).GetMemberPath(noError: true); - argumets[0] = Expression.Constant($"Member: {membername} or {memEx.Member.Name} by path: {memberPath} was null!; Mapping Types: {sourceType} to {resultType}"); - + argumets[0] = Expression.Constant($"Member: .{membername} or .{memEx.Member.Name} by path: {labelSource}.{memberPath} was null!; Mapping Types: {sourceType} to {resultType}"); } else { var memberPath = Expression.Lambda(expr).GetMemberPath(noError: true); - argumets[0] = Expression.Constant($"Member: {membername} by path: {memberPath} was null!; Mapping Types: {sourceType} to {resultType}"); + argumets[0] = Expression.Constant($"Member: .{membername} by path: .{memberPath} was null!; Mapping Types: {labelSource}.{sourceType} to {resultType}"); } result = Expression.IfThenElse(compareNull, Expression @@ -423,6 +426,7 @@ public static Expression ApplyThrowPropagation(this Expression getter) memberNestingLevel++; } + return result; } return getter; } From f504b7508b9e4fbb1b2d1303c01a7262c74be19f Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 29 May 2025 10:45:36 +0500 Subject: [PATCH 5/5] Fix Description to Throw for next member level --- src/Mapster/Utils/ExpressionEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index f3d2294b..93f5106c 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -415,7 +415,7 @@ public static Expression ApplyThrowPropagation(this Expression getter) else { var memberPath = Expression.Lambda(expr).GetMemberPath(noError: true); - argumets[0] = Expression.Constant($"Member: .{membername} by path: .{memberPath} was null!; Mapping Types: {labelSource}.{sourceType} to {resultType}"); + argumets[0] = Expression.Constant($"Member: .{membername} by path: {labelSource}.{memberPath} was null!; Mapping Types: {sourceType} to {resultType}"); } result = Expression.IfThenElse(compareNull, Expression