From 623f9405a6a90e9cb9c3de901ceb116bdddcae2b Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 21 Jan 2025 14:38:39 +0500 Subject: [PATCH 1/6] add Ignor Ctor Param --- src/Mapster/Adapters/BaseClassAdapter.cs | 30 ++++++++++++++++--- src/Mapster/Adapters/ClassAdapter.cs | 6 ++-- .../Adapters/ReadOnlyInterfaceAdapter.cs | 2 +- src/Mapster/Adapters/RecordTypeAdapter.cs | 4 +-- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index faa490ec..c0ee6a46 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -15,7 +15,7 @@ internal abstract class BaseClassAdapter : BaseAdapter #region Build the Adapter Model - protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null) + protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null, bool CtorMapping = false) { var destinationMembers = classModel.Members; var unmappedDestinationMembers = new List(); @@ -29,7 +29,7 @@ src is LambdaExpression lambda : ExpressionEx.PropertyOrFieldPath(source, (string)src))); foreach (var destinationMember in destinationMembers) { - if (ProcessIgnores(arg, destinationMember, out var ignore)) + if (ProcessIgnores(arg, destinationMember, out var ignore) && !CtorMapping) continue; var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable(); @@ -80,7 +80,8 @@ select fn(src, destinationMember, arg)) { if (classModel.BreakOnUnmatched) return null!; - unmappedDestinationMembers.Add(destinationMember.Name); + if(!arg.Settings.Ignore.Any(x=>x.Key == destinationMember.Name)) // Don't mark a constructor parameter if it was explicitly ignored + unmappedDestinationMembers.Add(destinationMember.Name); } properties.Add(propertyModel); @@ -128,7 +129,7 @@ protected static bool ProcessIgnores( && ignore.Condition == null; } - protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg) + protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg, Expression? destination) { var members = classConverter.Members; @@ -156,6 +157,27 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi var condition = ExpressionEx.Not(body); getter = Expression.Condition(condition, getter, defaultConst); } + else + if (arg.Settings.Ignore.Count != 0) + { + if (arg.MapType != MapType.MapToTarget && arg.Settings.Ignore.Any(x => x.Key == member.DestinationMember.Name)) + getter = defaultConst; + + if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) + { + if (arg.Settings.Ignore.Any(x => x.Key == member.DestinationMember.Name)) + { + var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() + .Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault(); + + if (find != null) + getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); + } + else + getter = defaultConst; + } + + } } arguments.Add(getter); } diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 407c5a5b..d953f11a 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -69,19 +69,19 @@ protected override Expression CreateInstantiationExpression(Expression source, E classConverter = destType.GetConstructors() .OrderByDescending(it => it.GetParameters().Length) .Select(it => GetConstructorModel(it, true)) - .Select(it => CreateClassConverter(source, it, arg)) + .Select(it => CreateClassConverter(source, it, arg, CtorMapping:true)) .FirstOrDefault(it => it != null); } else { var model = GetConstructorModel(ctor, false); - classConverter = CreateClassConverter(source, model, arg); + classConverter = CreateClassConverter(source, model, arg, CtorMapping:true); } if (classConverter == null) return base.CreateInstantiationExpression(source, destination, arg); - return CreateInstantiationExpression(source, classConverter, arg); + return CreateInstantiationExpression(source, classConverter, arg, destination); } protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) diff --git a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs index 3703c281..bf097fb5 100644 --- a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs +++ b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs @@ -39,7 +39,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E var ctor = destType.GetConstructors()[0]; var classModel = GetConstructorModel(ctor, false); var classConverter = CreateClassConverter(source, classModel, arg); - return CreateInstantiationExpression(source, classConverter, arg); + return CreateInstantiationExpression(source, classConverter, arg, destination); } else return base.CreateInstantiationExpression(source,destination, arg); diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 009af932..718000f4 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -31,8 +31,8 @@ protected override Expression CreateInstantiationExpression(Expression source, E var ctor = destType.GetConstructors() .OrderByDescending(it => it.GetParameters().Length).ToArray().FirstOrDefault(); // Will be used public constructor with the maximum number of parameters var classModel = GetConstructorModel(ctor, false); - var classConverter = CreateClassConverter(source, classModel, arg); - var installExpr = CreateInstantiationExpression(source, classConverter, arg); + var classConverter = CreateClassConverter(source, classModel, arg, CtorMapping:true); + var installExpr = CreateInstantiationExpression(source, classConverter, arg, destination); return RecordInlineExpression(source, arg, installExpr); // Activator field when not include in public ctor } From 5e9b8dffedf7cf847e2c35e522676d787686095d Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 21 Jan 2025 16:31:00 +0500 Subject: [PATCH 2/6] add Test --- src/Mapster.Tests/WhenIgnoreMapping.cs | 67 +++++++++++++++++++ .../WhenMappingRecordRegression.cs | 24 +++++++ 2 files changed, 91 insertions(+) diff --git a/src/Mapster.Tests/WhenIgnoreMapping.cs b/src/Mapster.Tests/WhenIgnoreMapping.cs index 585e911c..6398ef35 100644 --- a/src/Mapster.Tests/WhenIgnoreMapping.cs +++ b/src/Mapster.Tests/WhenIgnoreMapping.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Shouldly; @@ -55,6 +57,69 @@ public void TestIgnoreMember() poco2.Name.ShouldBeNull(); } + /// + /// https://github.com/MapsterMapper/Mapster/issues/707 + /// + [TestMethod] + public void WhenClassIgnoreCtorParamGetDefaultValue() + { + var config = new TypeAdapterConfig() + { + RequireDestinationMemberSource = true, + }; + config.Default + .NameMatchingStrategy(new NameMatchingStrategy + { + SourceMemberNameConverter = input => input.ToLowerInvariant(), + DestinationMemberNameConverter = input => input.ToLowerInvariant(), + }) + ; + config + .NewConfig() + .MapToConstructor(GetConstructor()) + .Ignore(e => e.Id); + + var source = new A707 { Text = "test" }; + var dest = new B707(123, "Hello"); + + var docKind = source.Adapt(config); + var mapTotarget = source.Adapt(dest,config); + + docKind.Id.ShouldBe(0); + mapTotarget.Id.ShouldBe(123); + mapTotarget.Text.ShouldBe("test"); + } + + #region TestClasses + static ConstructorInfo? GetConstructor() + { + var parameterlessCtorInfo = typeof(TDestination).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, new Type[0]); + + var ctors = typeof(TDestination).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + var validCandidateCtors = ctors.Except(new[] { parameterlessCtorInfo }).ToArray(); + var ctorToUse = validCandidateCtors.Length == 1 + ? validCandidateCtors.First() + : validCandidateCtors.OrderByDescending(c => c.GetParameters().Length).First(); + + return ctorToUse; + } + public class A707 + { + public string? Text { get; set; } + } + + public class B707 + { + public int Id { get; private set; } + public string Text { get; private set; } + + public B707(int id, string text) + { + Id = id; + Text = text; + } + } + public class Poco { public Guid Id { get; set; } @@ -67,5 +132,7 @@ public class Dto [JsonIgnore] public string Name { get; set; } } + + #endregion TestClasses } } diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs index e5d719e4..f4658e85 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -353,6 +353,25 @@ public void MappingInterfaceToInterface() } + /// + /// https://github.com/MapsterMapper/Mapster/issues/456 + /// + [TestMethod] + public void WhenRecordReceivedIgnoreCtorParamProcessing() + { + TypeAdapterConfig.NewConfig() + .Ignore(dest => dest.Name); + + var userDto = new UserDto456("Amichai"); + var user = new UserRecord456("John"); + + var map = userDto.Adapt(); + var maptoTarget = userDto.Adapt(user); + + map.Name.ShouldBeNullOrEmpty(); + maptoTarget.Name.ShouldBe("John"); + } + #region NowNotWorking @@ -382,6 +401,11 @@ public void CollectionUpdate() #region TestClasses + + public record UserRecord456(string Name); + + public record UserDto456(string Name); + public interface IActivityDataExtentions : IActivityData { public int TempLength { get; set; } From 0685bb31ad4175295292761fab993e78cf0db2b7 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 22 Jan 2025 14:13:16 +0500 Subject: [PATCH 3/6] refactoring algorithm --- src/Mapster/Adapters/BaseClassAdapter.cs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index c0ee6a46..67e3caf4 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -158,25 +158,18 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = Expression.Condition(condition, getter, defaultConst); } else - if (arg.Settings.Ignore.Count != 0) + if (arg.Settings.Ignore.Any(x => x.Key == member.DestinationMember.Name)) { - if (arg.MapType != MapType.MapToTarget && arg.Settings.Ignore.Any(x => x.Key == member.DestinationMember.Name)) - getter = defaultConst; + getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) { - if (arg.Settings.Ignore.Any(x => x.Key == member.DestinationMember.Name)) - { - var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() - .Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault(); - - if (find != null) - getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); - } - else - getter = defaultConst; - } + var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() + .Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault(); + if (find != null) + getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); + } } } arguments.Add(getter); From fdf059ac6347e4e4df1ab282cec42b4f9c4bce73 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 22 Jan 2025 15:20:17 +0500 Subject: [PATCH 4/6] add supporting Ignored() to readonly interface --- src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs index bf097fb5..987cfe77 100644 --- a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs +++ b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs @@ -38,7 +38,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E return base.CreateInstantiationExpression(source, destination, arg); var ctor = destType.GetConstructors()[0]; var classModel = GetConstructorModel(ctor, false); - var classConverter = CreateClassConverter(source, classModel, arg); + var classConverter = CreateClassConverter(source, classModel, arg,CtorMapping:true); return CreateInstantiationExpression(source, classConverter, arg, destination); } else From 6cf1320c4d23e76bf791619e1163e2ca2ff4db1e Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 22 Jan 2025 19:23:23 +0500 Subject: [PATCH 5/6] add test Interface Ctor param Ignored() --- src/Mapster.Tests/WhenIgnoreMapping.cs | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/Mapster.Tests/WhenIgnoreMapping.cs b/src/Mapster.Tests/WhenIgnoreMapping.cs index 6398ef35..245c4e63 100644 --- a/src/Mapster.Tests/WhenIgnoreMapping.cs +++ b/src/Mapster.Tests/WhenIgnoreMapping.cs @@ -90,7 +90,47 @@ public void WhenClassIgnoreCtorParamGetDefaultValue() mapTotarget.Text.ShouldBe("test"); } + /// + /// https://github.com/MapsterMapper/Mapster/issues/723 + /// + [TestMethod] + public void MappingToIntefaceWithIgnorePrivateSetProperty() + { + TypeAdapterConfig + .NewConfig() + .TwoWays() + .Ignore(dest => dest.Ignore); + + InterfaceDestination723 dataDestination = new Data723() { Inter = "IterDataDestination", Ignore = "IgnoreDataDestination" }; + + Should.NotThrow(() => + { + var isourse = dataDestination.Adapt(); + var idestination = dataDestination.Adapt(); + }); + + } + #region TestClasses + + public interface InterfaceDestination723 + { + public string Inter { get; set; } + public string Ignore { get; } + } + + public interface InterfaceSource723 + { + public string Inter { get; set; } + } + + private class Data723 : InterfaceSource723, InterfaceDestination723 + { + public string Ignore { get; set; } + + public string Inter { get; set; } + } + static ConstructorInfo? GetConstructor() { var parameterlessCtorInfo = typeof(TDestination).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, new Type[0]); From 7f6cdde318d1233768f88367daf3ec4258c23e02 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 22 Jan 2025 19:28:11 +0500 Subject: [PATCH 6/6] fix misprints --- src/Mapster/Adapters/BaseClassAdapter.cs | 4 ++-- src/Mapster/Adapters/ClassAdapter.cs | 4 ++-- src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs | 2 +- src/Mapster/Adapters/RecordTypeAdapter.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 67e3caf4..966f2fe9 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -15,7 +15,7 @@ internal abstract class BaseClassAdapter : BaseAdapter #region Build the Adapter Model - protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null, bool CtorMapping = false) + protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null, bool ctorMapping = false) { var destinationMembers = classModel.Members; var unmappedDestinationMembers = new List(); @@ -29,7 +29,7 @@ src is LambdaExpression lambda : ExpressionEx.PropertyOrFieldPath(source, (string)src))); foreach (var destinationMember in destinationMembers) { - if (ProcessIgnores(arg, destinationMember, out var ignore) && !CtorMapping) + if (ProcessIgnores(arg, destinationMember, out var ignore) && !ctorMapping) continue; var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable(); diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index d953f11a..6ae450fa 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -69,13 +69,13 @@ protected override Expression CreateInstantiationExpression(Expression source, E classConverter = destType.GetConstructors() .OrderByDescending(it => it.GetParameters().Length) .Select(it => GetConstructorModel(it, true)) - .Select(it => CreateClassConverter(source, it, arg, CtorMapping:true)) + .Select(it => CreateClassConverter(source, it, arg, ctorMapping:true)) .FirstOrDefault(it => it != null); } else { var model = GetConstructorModel(ctor, false); - classConverter = CreateClassConverter(source, model, arg, CtorMapping:true); + classConverter = CreateClassConverter(source, model, arg, ctorMapping:true); } if (classConverter == null) diff --git a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs index 987cfe77..ea4aa6e1 100644 --- a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs +++ b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs @@ -38,7 +38,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E return base.CreateInstantiationExpression(source, destination, arg); var ctor = destType.GetConstructors()[0]; var classModel = GetConstructorModel(ctor, false); - var classConverter = CreateClassConverter(source, classModel, arg,CtorMapping:true); + var classConverter = CreateClassConverter(source, classModel, arg, ctorMapping:true); return CreateInstantiationExpression(source, classConverter, arg, destination); } else diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 718000f4..0ef0532d 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -31,7 +31,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E var ctor = destType.GetConstructors() .OrderByDescending(it => it.GetParameters().Length).ToArray().FirstOrDefault(); // Will be used public constructor with the maximum number of parameters var classModel = GetConstructorModel(ctor, false); - var classConverter = CreateClassConverter(source, classModel, arg, CtorMapping:true); + var classConverter = CreateClassConverter(source, classModel, arg, ctorMapping:true); var installExpr = CreateInstantiationExpression(source, classConverter, arg, destination); return RecordInlineExpression(source, arg, installExpr); // Activator field when not include in public ctor }