From 623f9405a6a90e9cb9c3de901ceb116bdddcae2b Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 21 Jan 2025 14:38:39 +0500 Subject: [PATCH 01/18] 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 02/18] 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 03/18] 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 04/18] 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 05/18] 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 06/18] 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 } From 0f2302ab62c81a4118aef3211feb855bd4a1b16f Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 23 Jan 2025 21:17:31 +0500 Subject: [PATCH 07/18] fix maToTarget behavior From RecordType --- src/Mapster/Adapters/BaseClassAdapter.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index faa490ec..2706bb55 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -54,6 +54,15 @@ select fn(src, destinationMember, arg)) Destination = (ParameterExpression?)destination, UseDestinationValue = arg.MapType != MapType.Projection && destinationMember.UseDestinationValue(arg), }; + if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType()) + { + var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() + .Where(x => x.Name == destinationMember.Name).FirstOrDefault(); + + if (find != null) + getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); + + } if (getter != null) { propertyModel.Getter = arg.MapType == MapType.Projection From f8469e911789669c46d8f745a083278c325eb049 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 23 Jan 2025 21:38:32 +0500 Subject: [PATCH 08/18] add To RecordTypeAdapter --- src/Mapster/Adapters/RecordTypeAdapter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 009af932..12617786 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -33,7 +33,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E var classModel = GetConstructorModel(ctor, false); var classConverter = CreateClassConverter(source, classModel, arg); var installExpr = CreateInstantiationExpression(source, classConverter, arg); - return RecordInlineExpression(source, arg, installExpr); // Activator field when not include in public ctor + return RecordInlineExpression(source, destination, arg, installExpr); // Activator field when not include in public ctor } protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) @@ -46,7 +46,7 @@ protected override Expression CreateInlineExpression(Expression source, CompileA return base.CreateInstantiationExpression(source, arg); } - private Expression? RecordInlineExpression(Expression source, CompileArgument arg, Expression installExpr) + private Expression? RecordInlineExpression(Expression source, Expression? destination, CompileArgument arg, Expression installExpr) { //new TDestination { // Prop1 = convert(src.Prop1), @@ -58,7 +58,7 @@ protected override Expression CreateInlineExpression(Expression source, CompileA var newInstance = memberInit?.NewExpression ?? (NewExpression)exp; var contructorMembers = newInstance.Arguments.OfType().Select(me => me.Member).ToArray(); var classModel = GetSetterModel(arg); - var classConverter = CreateClassConverter(source, classModel, arg); + var classConverter = CreateClassConverter(source, classModel, arg, destination:destination); var members = classConverter.Members; var lines = new List(); From d02756f76f9da73122be63fdd30bfe2264b3e735 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 23 Jan 2025 22:04:21 +0500 Subject: [PATCH 09/18] fix mapToTarget test from Record --- src/Mapster.Tests/WhenMappingRecordRegression.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs index e5d719e4..1bd362eb 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -15,10 +15,10 @@ public class WhenMappingRecordRegression public void AdaptRecordToRecord() { var _source = new TestRecord() { X = 700 }; - var _destination = new TestRecord() { X = 500 }; + var _destination = new TestRecordY() { X = 500 , Y = 200 }; var _result = _source.Adapt(_destination); - _result.X.ShouldBe(700); + _result.Y.ShouldBe(200); object.ReferenceEquals(_result, _destination).ShouldBeFalse(); } @@ -382,6 +382,11 @@ public void CollectionUpdate() #region TestClasses + public record TestRecordY() + { + public int X { get; set; } + public int Y { get; set; } + } public interface IActivityDataExtentions : IActivityData { public int TempLength { get; set; } From 564da6d75b32505703e0f6779e99c4db1c693056 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 24 Jan 2025 16:21:29 +0500 Subject: [PATCH 10/18] fix map to target from recordType --- .../WhenIgnoringConditionally.cs | 6 +- .../WhenMappingRecordRegression.cs | 43 ++++++++++-- src/Mapster/Adapters/BaseClassAdapter.cs | 11 +++- src/Mapster/Adapters/RecordTypeAdapter.cs | 66 ++++++++++++++----- 4 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/Mapster.Tests/WhenIgnoringConditionally.cs b/src/Mapster.Tests/WhenIgnoringConditionally.cs index 471760b8..9014d133 100644 --- a/src/Mapster.Tests/WhenIgnoringConditionally.cs +++ b/src/Mapster.Tests/WhenIgnoringConditionally.cs @@ -164,6 +164,8 @@ public void IgnoreIf_Apply_To_RecordType() .Compile(); var poco = new SimplePoco { Id = 1, Name = "TestName" }; + + var srt = poco.BuildAdapter().CreateMapToTargetExpression(); var dto = TypeAdapter.Adapt(poco); dto.Id.ShouldBe(1); @@ -190,8 +192,8 @@ public class SimpleDto [AdaptWith(AdaptDirectives.DestinationAsRecord)] public class SimpleRecord { - public int Id { get; } - public string Name { get; } + public int Id { get; private set; } + public string Name { get; private set; } public SimpleRecord(int id, string name) { diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs index aced8129..e557927c 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -14,12 +14,24 @@ public class WhenMappingRecordRegression [TestMethod] public void AdaptRecordToRecord() { + TypeAdapterConfig + .NewConfig() + .Ignore(dest => dest.Y); + + + + var _source = new TestRecord() { X = 700 }; var _destination = new TestRecordY() { X = 500 , Y = 200 }; + + var _destination2 = new TestRecordY() { X = 300, Y = 400 }; var _result = _source.Adapt(_destination); - _result.X.ShouldBe(700); - _result.Y.ShouldBe(200); - object.ReferenceEquals(_result, _destination).ShouldBeFalse(); + + var result2 = _destination.Adapt(_destination2); + + //_result.X.ShouldBe(700); + //_result.Y.ShouldBe(200); + //object.ReferenceEquals(_result, _destination).ShouldBeFalse(); } [TestMethod] @@ -362,14 +374,28 @@ public void WhenRecordReceivedIgnoreCtorParamProcessing() TypeAdapterConfig.NewConfig() .Ignore(dest => dest.Name); + //TypeAdapterConfig.NewConfig() + //.Ignore(dest => dest.User); + var userDto = new UserDto456("Amichai"); var user = new UserRecord456("John"); - var map = userDto.Adapt(); - var maptoTarget = userDto.Adapt(user); + //var map = userDto.Adapt(); - map.Name.ShouldBeNullOrEmpty(); - maptoTarget.Name.ShouldBe("John"); + // var sd = userDto.BuildAdapter().CreateMapToTargetExpression(); + // var maptoTarget = userDto.Adapt(user); + + + var s = new DToInside(userDto); + + var d = new UserInside(user, new UserRecord456( "Skot")); + + var sd = s.BuildAdapter().CreateMapToTargetExpression(); + + var ssd = s.Adapt(d); + + // map.Name.ShouldBeNullOrEmpty(); + // maptoTarget.Name.ShouldBe("John"); } @@ -406,6 +432,9 @@ public record TestRecordY() public int Y { get; set; } } + public record UserInside(UserRecord456 User, UserRecord456 SecondName); + public record DToInside(UserDto456 User); + public record UserRecord456(string Name); public record UserDto456(string Name); diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index d500ca58..09176e62 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -59,7 +59,7 @@ select fn(src, destinationMember, arg)) var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() .Where(x => x.Name == destinationMember.Name).FirstOrDefault(); - if (find != null) + if (find != null && destination != null) getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); } @@ -154,6 +154,15 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi if (member.Getter == null) { getter = defaultConst; + + if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) + { + var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() + .Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault(); + + if (find != null && destination != null) + getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); + } } else { diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 44b42226..1a282ea1 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Mapster.Utils; +using static Mapster.IgnoreDictionary; namespace Mapster.Adapters { @@ -20,19 +22,18 @@ protected override Expression CreateInstantiationExpression(Expression source, E { //new TDestination(src.Prop1, src.Prop2) - if (arg.GetConstructUsing() != null) - return base.CreateInstantiationExpression(source, destination, arg); - - var destType = arg.DestinationType.GetTypeInfo().IsInterface - ? DynamicTypeGenerator.GetTypeForInterface(arg.DestinationType, arg.Settings.Includes.Count > 0) - : arg.DestinationType; - if (destType == null) - return base.CreateInstantiationExpression(source, destination, arg); - 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 installExpr = CreateInstantiationExpression(source, classConverter, arg, destination); + Expression installExpr; + + if (arg.GetConstructUsing() != null || arg.DestinationType == null) + installExpr = base.CreateInstantiationExpression(source, destination, arg); + else + { + var ctor = arg.DestinationType.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); + installExpr = CreateInstantiationExpression(source, classConverter, arg, destination); + } return RecordInlineExpression(source, destination, arg, installExpr); // Activator field when not include in public ctor } @@ -56,7 +57,9 @@ protected override Expression CreateInlineExpression(Expression source, CompileA var exp = installExpr; var memberInit = exp as MemberInitExpression; var newInstance = memberInit?.NewExpression ?? (NewExpression)exp; - var contructorMembers = newInstance.Arguments.OfType().Select(me => me.Member).ToArray(); + var contructorMembers = arg.DestinationType.GetConstructors() + .OrderByDescending(it => it.GetParameters().Length).FirstOrDefault() + .GetParameters().ToList(); var classModel = GetSetterModel(arg); var classConverter = CreateClassConverter(source, classModel, arg, destination:destination); var members = classConverter.Members; @@ -70,7 +73,7 @@ protected override Expression CreateInlineExpression(Expression source, CompileA return null; if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) - && member.Getter is MemberExpression memberExp && contructorMembers.Contains(memberExp.Member)) + && contructorMembers.Any(x=>string.Equals(x.Name, member.DestinationMember.Name, StringComparison.InvariantCultureIgnoreCase))) continue; if (member.DestinationMember.SetterModifier == AccessModifier.None) @@ -93,8 +96,39 @@ protected override Expression CreateInlineExpression(Expression source, CompileA lines.Add(bind); } + if(arg.MapType == MapType.MapToTarget) + lines.AddRange(RecordIngnoredWithoutConditonRestore(destination, arg, contructorMembers)); + return Expression.MemberInit(newInstance, lines); } + + private List RecordIngnoredWithoutConditonRestore(Expression? destination, CompileArgument arg, List contructorMembers) + { + var members = arg.DestinationType + .GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()) + .Where(x=> arg.Settings.Ignore.Any(y=> y.Key == x.Name)); + + var lines = new List(); + + + foreach (var member in members) + { + if(destination == null) + continue; + + IgnoreItem ignore; + ProcessIgnores(arg, member, out ignore); + + if (member.SetterModifier == AccessModifier.None || + ignore.Condition != null || + contructorMembers.Any(x=> string.Equals(x.Name, member.Name, StringComparison.InvariantCultureIgnoreCase))) + continue; + + lines.Add(Expression.Bind((MemberInfo)member.Info, Expression.MakeMemberAccess(destination, (MemberInfo)member.Info))); + } + + return lines; + } } } From f29d93558b4acbd36ee757270b983b1f669f2fe8 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 25 Jan 2025 13:24:01 +0500 Subject: [PATCH 11/18] update RecordType Tests using ignore ctor param and test MapToTarget and not match value --- .../WhenMappingRecordRegression.cs | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs index e557927c..2316069b 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -18,9 +18,6 @@ public void AdaptRecordToRecord() .NewConfig() .Ignore(dest => dest.Y); - - - var _source = new TestRecord() { X = 700 }; var _destination = new TestRecordY() { X = 500 , Y = 200 }; @@ -29,9 +26,9 @@ public void AdaptRecordToRecord() var result2 = _destination.Adapt(_destination2); - //_result.X.ShouldBe(700); - //_result.Y.ShouldBe(200); - //object.ReferenceEquals(_result, _destination).ShouldBeFalse(); + _result.X.ShouldBe(700); + _result.Y.ShouldBe(200); + object.ReferenceEquals(_result, _destination).ShouldBeFalse(); } [TestMethod] @@ -374,28 +371,24 @@ public void WhenRecordReceivedIgnoreCtorParamProcessing() TypeAdapterConfig.NewConfig() .Ignore(dest => dest.Name); - //TypeAdapterConfig.NewConfig() - //.Ignore(dest => dest.User); + TypeAdapterConfig.NewConfig() + .Ignore(dest => dest.User); var userDto = new UserDto456("Amichai"); var user = new UserRecord456("John"); + var DtoInsider = new DtoInside(userDto); + var UserInsider = new UserInside(user, new UserRecord456("Skot")); - //var map = userDto.Adapt(); - - // var sd = userDto.BuildAdapter().CreateMapToTargetExpression(); - // var maptoTarget = userDto.Adapt(user); - - - var s = new DToInside(userDto); - - var d = new UserInside(user, new UserRecord456( "Skot")); + var map = userDto.Adapt(); + var maptoTarget = userDto.Adapt(user); - var sd = s.BuildAdapter().CreateMapToTargetExpression(); + var MapToTargetInsider = DtoInsider.Adapt(UserInsider); - var ssd = s.Adapt(d); + map.Name.ShouldBeNullOrEmpty(); // Ignore is work set default value + maptoTarget.Name.ShouldBe("John"); // Ignore is work ignored member save value from Destination + MapToTargetInsider.User.Name.ShouldBe("John"); // Ignore is work member save value from Destination + MapToTargetInsider.SecondName.Name.ShouldBe("Skot"); // Unmached member save value from Destination - // map.Name.ShouldBeNullOrEmpty(); - // maptoTarget.Name.ShouldBe("John"); } @@ -433,7 +426,7 @@ public record TestRecordY() } public record UserInside(UserRecord456 User, UserRecord456 SecondName); - public record DToInside(UserDto456 User); + public record DtoInside(UserDto456 User); public record UserRecord456(string Name); From 0c5384dbaaffe02c75c8a9f0a59329544faf5d86 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 28 Jan 2025 14:49:37 +0500 Subject: [PATCH 12/18] refactoring Map to target record --- src/Mapster/Adapters/BaseClassAdapter.cs | 47 +++++++++++------------ src/Mapster/Adapters/RecordTypeAdapter.cs | 17 ++++---- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 09176e62..9bb5bc17 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -1,10 +1,10 @@ -using System; +using Mapster.Models; +using Mapster.Utils; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Mapster.Models; -using Mapster.Utils; namespace Mapster.Adapters { @@ -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, ClassModel recordRestorMemberModel = null) { var destinationMembers = classModel.Members; var unmappedDestinationMembers = new List(); @@ -56,12 +56,7 @@ select fn(src, destinationMember, arg)) }; if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType()) { - var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() - .Where(x => x.Name == destinationMember.Name).FirstOrDefault(); - - if (find != null && destination != null) - getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); - + getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination) ?? getter; } if (getter != null) { @@ -138,7 +133,7 @@ protected static bool ProcessIgnores( && ignore.Condition == null; } - protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg, Expression? destination) + protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg, Expression? destination, ClassModel recordRestorParamModel = null) { var members = classConverter.Members; @@ -156,13 +151,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) - { - var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray() - .Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault(); - - if (find != null && destination != null) - getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); - } + getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination) ?? getter; } else { @@ -181,13 +170,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) - { - 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); - } + getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination) ?? getter; } } arguments.Add(getter); @@ -214,6 +197,20 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) }; } + protected MemberExpression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination) + { + if (restorRecordModel != null && destination != null) + { + var find = restorRecordModel.Members + .Where(x => x.Name == member.Name).FirstOrDefault(); + + if (find != null) + return Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); + } + + return null; + } + #endregion } } diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 1a282ea1..34edbdd2 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Mapster.Models; using Mapster.Utils; using static Mapster.IgnoreDictionary; @@ -31,8 +32,9 @@ protected override Expression CreateInstantiationExpression(Expression source, E var ctor = arg.DestinationType.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 restorParamModel = GetSetterModel(arg); var classConverter = CreateClassConverter(source, classModel, arg, ctorMapping: true); - installExpr = CreateInstantiationExpression(source, classConverter, arg, destination); + installExpr = CreateInstantiationExpression(source, classConverter, arg, destination, restorParamModel); } return RecordInlineExpression(source, destination, arg, installExpr); // Activator field when not include in public ctor } @@ -57,11 +59,9 @@ protected override Expression CreateInlineExpression(Expression source, CompileA var exp = installExpr; var memberInit = exp as MemberInitExpression; var newInstance = memberInit?.NewExpression ?? (NewExpression)exp; - var contructorMembers = arg.DestinationType.GetConstructors() - .OrderByDescending(it => it.GetParameters().Length).FirstOrDefault() - .GetParameters().ToList(); + var contructorMembers = newInstance.Constructor?.GetParameters().ToList() ?? new(); var classModel = GetSetterModel(arg); - var classConverter = CreateClassConverter(source, classModel, arg, destination:destination); + var classConverter = CreateClassConverter(source, classModel, arg, destination:destination,recordRestorMemberModel:classModel); var members = classConverter.Members; var lines = new List(); @@ -97,15 +97,14 @@ protected override Expression CreateInlineExpression(Expression source, CompileA } if(arg.MapType == MapType.MapToTarget) - lines.AddRange(RecordIngnoredWithoutConditonRestore(destination, arg, contructorMembers)); + lines.AddRange(RecordIngnoredWithoutConditonRestore(destination, arg, contructorMembers, classModel)); return Expression.MemberInit(newInstance, lines); } - private List RecordIngnoredWithoutConditonRestore(Expression? destination, CompileArgument arg, List contructorMembers) + private List RecordIngnoredWithoutConditonRestore(Expression? destination, CompileArgument arg, List contructorMembers, ClassModel restorPropertyModel) { - var members = arg.DestinationType - .GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()) + var members = restorPropertyModel.Members .Where(x=> arg.Settings.Ignore.Any(y=> y.Key == x.Name)); var lines = new List(); From f5a3ce6cd315f2bb25b4c1d30e313ec8b3fb6ff5 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 28 Jan 2025 19:14:31 +0500 Subject: [PATCH 13/18] Fix error with Using Destination Value From Record Type --- src/Mapster/Adapters/RecordTypeAdapter.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 34edbdd2..c9887f24 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -69,17 +70,19 @@ protected override Expression CreateInlineExpression(Expression source, CompileA lines.AddRange(memberInit.Bindings); foreach (var member in members) { - if (member.UseDestinationValue) - return null; + Expression? value; if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) && contructorMembers.Any(x=>string.Equals(x.Name, member.DestinationMember.Name, StringComparison.InvariantCultureIgnoreCase))) continue; - if (member.DestinationMember.SetterModifier == AccessModifier.None) + if (member.DestinationMember.SetterModifier == AccessModifier.None && member.UseDestinationValue == false) continue; - var value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + if (member.DestinationMember.SetterModifier == AccessModifier.None && member.UseDestinationValue) + continue; // work in progress + else + value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); //special null property check for projection //if we don't set null to property, EF will create empty object From 5c2d4a7c090fc9b8ab624e993239f65bf7e44740 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sun, 2 Feb 2025 08:24:31 +0500 Subject: [PATCH 14/18] add Null check destination from mapToTarget RecordType --- src/Mapster/Adapters/BaseClassAdapter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 9bb5bc17..309687a5 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -197,7 +197,7 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) }; } - protected MemberExpression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination) + protected Expression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination) { if (restorRecordModel != null && destination != null) { @@ -205,7 +205,11 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) .Where(x => x.Name == member.Name).FirstOrDefault(); if (find != null) - return Expression.MakeMemberAccess(destination, (MemberInfo)find.Info); + { + var compareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type)); + return Expression.Condition(compareNull, member.Type.CreateDefault(), Expression.MakeMemberAccess(destination, (MemberInfo)find.Info)); + } + } return null; From 32f0a3412c9f5687bd90eaeba3e24a8e052ed661 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sun, 2 Feb 2025 08:43:48 +0500 Subject: [PATCH 15/18] add support from RecordType IgnoreNullValue UseDestinationValue Use Destination value from Primitive type AutoProperty --- src/Mapster/Adapters/RecordTypeAdapter.cs | 206 ++++++++++++++++++---- 1 file changed, 175 insertions(+), 31 deletions(-) diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index c9887f24..7411c097 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -1,17 +1,17 @@ -using System; +using Mapster.Models; +using Mapster.Utils; +using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Mapster.Models; -using Mapster.Utils; using static Mapster.IgnoreDictionary; namespace Mapster.Adapters { internal class RecordTypeAdapter : ClassAdapter { + private ClassMapping? ClassConverterContext; protected override int Score => -149; protected override bool UseTargetValue => false; @@ -20,6 +20,15 @@ protected override bool CanMap(PreCompileArgument arg) return arg.DestinationType.IsRecordType(); } + protected override bool CanInline(Expression source, Expression? destination, CompileArgument arg) + { + return false; + } + + protected override Expression CreateInlineExpression(Expression source, CompileArgument arg) + { + return base.CreateInstantiationExpression(source, arg); + } protected override Expression CreateInstantiationExpression(Expression source, Expression? destination, CompileArgument arg) { //new TDestination(src.Prop1, src.Prop2) @@ -37,20 +46,12 @@ protected override Expression CreateInstantiationExpression(Expression source, E var classConverter = CreateClassConverter(source, classModel, arg, ctorMapping: true); installExpr = CreateInstantiationExpression(source, classConverter, arg, destination, restorParamModel); } - return RecordInlineExpression(source, destination, arg, installExpr); // Activator field when not include in public ctor - } - protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) - { - return Expression.Empty(); - } - protected override Expression CreateInlineExpression(Expression source, CompileArgument arg) - { - return base.CreateInstantiationExpression(source, arg); + return RecordInlineExpression(source, destination, arg, installExpr); // Activator field when not include in public ctor } - - private Expression? RecordInlineExpression(Expression source, Expression? destination, CompileArgument arg, Expression installExpr) + + private Expression? RecordInlineExpression(Expression source, Expression? destination, CompileArgument arg, Expression installExpr) { //new TDestination { // Prop1 = convert(src.Prop1), @@ -62,27 +63,43 @@ protected override Expression CreateInlineExpression(Expression source, CompileA var newInstance = memberInit?.NewExpression ?? (NewExpression)exp; var contructorMembers = newInstance.Constructor?.GetParameters().ToList() ?? new(); var classModel = GetSetterModel(arg); - var classConverter = CreateClassConverter(source, classModel, arg, destination:destination,recordRestorMemberModel:classModel); + var classConverter = CreateClassConverter(source, classModel, arg, destination: destination, recordRestorMemberModel: classModel); var members = classConverter.Members; + ClassConverterContext = classConverter; + var lines = new List(); if (memberInit != null) lines.AddRange(memberInit.Bindings); foreach (var member in members) { - Expression? value; if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) - && contructorMembers.Any(x=>string.Equals(x.Name, member.DestinationMember.Name, StringComparison.InvariantCultureIgnoreCase))) + && contructorMembers.Any(x => string.Equals(x.Name, member.DestinationMember.Name, StringComparison.InvariantCultureIgnoreCase))) continue; - if (member.DestinationMember.SetterModifier == AccessModifier.None && member.UseDestinationValue == false) + if (member.DestinationMember.SetterModifier == AccessModifier.None) continue; - if (member.DestinationMember.SetterModifier == AccessModifier.None && member.UseDestinationValue) - continue; // work in progress - else - value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + + if (arg.MapType == MapType.MapToTarget && arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull()) // add IgnoreNullValues support + { + if (adapt is ConditionalExpression condEx) + { + if (condEx.Test is BinaryExpression { NodeType: ExpressionType.Equal } binEx && + binEx.Left == member.Getter && + binEx.Right is ConstantExpression { Value: null }) + adapt = condEx.IfFalse; + } + 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)); + adapt = Expression.Condition(sourceCondition, adapt, destinationCanbeNull); + } + + + //special null property check for projection //if we don't set null to property, EF will create empty object @@ -93,13 +110,13 @@ protected override Expression CreateInlineExpression(Expression source, CompileA && !member.DestinationMember.Type.IsCollection() && member.Getter.Type.GetTypeInfo().GetCustomAttributesData().All(attr => attr.GetAttributeType().Name != "ComplexTypeAttribute")) { - value = member.Getter.NotNullReturn(value); + adapt = member.Getter.NotNullReturn(adapt); } - var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, value); + var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, adapt); lines.Add(bind); } - if(arg.MapType == MapType.MapToTarget) + if (arg.MapType == MapType.MapToTarget) lines.AddRange(RecordIngnoredWithoutConditonRestore(destination, arg, contructorMembers, classModel)); return Expression.MemberInit(newInstance, lines); @@ -107,15 +124,15 @@ protected override Expression CreateInlineExpression(Expression source, CompileA private List RecordIngnoredWithoutConditonRestore(Expression? destination, CompileArgument arg, List contructorMembers, ClassModel restorPropertyModel) { - var members = restorPropertyModel.Members - .Where(x=> arg.Settings.Ignore.Any(y=> y.Key == x.Name)); + var members = restorPropertyModel.Members + .Where(x => arg.Settings.Ignore.Any(y => y.Key == x.Name)); var lines = new List(); foreach (var member in members) { - if(destination == null) + if (destination == null) continue; IgnoreItem ignore; @@ -123,14 +140,141 @@ private List RecordIngnoredWithoutConditonRestore(Expression? des if (member.SetterModifier == AccessModifier.None || ignore.Condition != null || - contructorMembers.Any(x=> string.Equals(x.Name, member.Name, StringComparison.InvariantCultureIgnoreCase))) + contructorMembers.Any(x => string.Equals(x.Name, member.Name, StringComparison.InvariantCultureIgnoreCase))) continue; - lines.Add(Expression.Bind((MemberInfo)member.Info, Expression.MakeMemberAccess(destination, (MemberInfo)member.Info))); + lines.Add(Expression.Bind((MemberInfo)member.Info, Expression.MakeMemberAccess(destination, (MemberInfo)member.Info))); } return lines; } + + protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) + { + // Mapping property Without setter when UseDestinationValue == true + + var result = destination; + var classModel = GetSetterModel(arg); + var classConverter = CreateClassConverter(source, classModel, arg, result); + var members = classConverter.Members; + + var lines = new List(); + + foreach (var member in members) + { + if (member.DestinationMember.SetterModifier == AccessModifier.None && member.UseDestinationValue) + { + + if (member.DestinationMember is PropertyModel && member.DestinationMember.Type.IsValueType + || member.DestinationMember.Type.IsMapsterPrimitive() + || member.DestinationMember.Type.IsRecordType()) + { + + Expression adapt; + if (member.DestinationMember.Type.IsRecordType()) + adapt = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, member.Getter); + else + adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, result); + + var blocks = Expression.Block(SetValueTypeAutoPropertyByReflection(member, adapt, classModel)); + var lambda = Expression.Lambda(blocks, parameters: new[] { (ParameterExpression)source, (ParameterExpression)destination }); + + if (arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull()) + { + + if (arg.MapType != MapType.MapToTarget) + { + var condition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); + lines.Add(Expression.IfThen(condition, Expression.Invoke(lambda, source, destination))); + continue; + } + + if (arg.MapType == MapType.MapToTarget) + { + var var2Param = ClassConverterContext.Members.Where(x => x.DestinationMember.Name == member.DestinationMember.Name).FirstOrDefault(); + + Expression destMemberVar2 = var2Param.DestinationMember.GetExpression(var2Param.Destination); + if(member.DestinationMember.Type.IsRecordType()) + destMemberVar2 = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, destMemberVar2); + var blocksVar2 = Expression.Block(SetValueTypeAutoPropertyByReflection(member, destMemberVar2, classModel)); + var lambdaVar2 = Expression.Lambda(blocksVar2, parameters: new[] { (ParameterExpression)var2Param.Destination, (ParameterExpression)destination }); + var adaptVar2 = Expression.Invoke(lambdaVar2, var2Param.Destination, destination); + + + Expression conditionVar2; + if (destMemberVar2.CanBeNull()) + { + var complexcheck = Expression.AndAlso(Expression.NotEqual(var2Param.Destination, Expression.Constant(null, var2Param.Destination.Type)), // if(var2 != null && var2.Prop != null) + Expression.NotEqual(destMemberVar2, Expression.Constant(null, var2Param.Getter.Type))); + conditionVar2 = Expression.IfThen(complexcheck, adaptVar2); + } + else + conditionVar2 = Expression.IfThen(Expression.NotEqual(var2Param.Destination, Expression.Constant(null, var2Param.Destination.Type)), adaptVar2); + + var condition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); + lines.Add(Expression.IfThenElse(condition, Expression.Invoke(lambda, source, destination), conditionVar2)); + continue; + } + } + + lines.Add(Expression.Invoke(lambda, source, destination)); + } + else + { + var destMember = member.DestinationMember.GetExpression(destination); + var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember); + + if (arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull()) + { + if (arg.MapType != MapType.MapToTarget) + { + var condition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); + lines.Add(Expression.IfThen(condition, adapt)); + continue; + } + if (arg.MapType == MapType.MapToTarget) + { + var var2Param = ClassConverterContext.Members.Where(x => x.DestinationMember.Name == member.DestinationMember.Name).FirstOrDefault(); + + var destMemberVar2 = var2Param.DestinationMember.GetExpression(var2Param.Destination); + var adaptVar2 = CreateAdaptExpression(destMemberVar2, member.DestinationMember.Type, arg, var2Param, destMember); + + var complexcheck = Expression.AndAlso(Expression.NotEqual(var2Param.Destination, Expression.Constant(null, var2Param.Destination.Type)), // if(var2 != null && var2.Prop != null) + Expression.NotEqual(destMemberVar2, Expression.Constant(null, var2Param.Getter.Type))); + var conditionVar2 = Expression.IfThen(complexcheck, adaptVar2); + + var condition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); + lines.Add(Expression.IfThenElse(condition, adapt, conditionVar2)); + continue; + } + + + } + + lines.Add(adapt); + } + + } + } + + return lines.Count > 0 ? (Expression)Expression.Block(lines) : Expression.Empty(); + } + + protected static Expression SetValueTypeAutoPropertyByReflection(MemberMapping member, Expression adapt, ClassModel checkmodel) + { + var modDesinationMemeberName = $"<{member.DestinationMember.Name}>k__BackingField"; + if (checkmodel.Members.Any(x => x.Name == modDesinationMemeberName) == false) // Property is not autoproperty + return Expression.Empty(); + var typeofExpression = Expression.Constant(member.Destination!.Type); + var getPropertyMethod = typeof(Type).GetMethod("GetField", new[] { typeof(string), typeof(BindingFlags) })!; + var getPropertyExpression = Expression.Call(typeofExpression, getPropertyMethod, + Expression.Constant(modDesinationMemeberName), Expression.Constant(BindingFlags.Instance | BindingFlags.NonPublic)); + var setValueMethod = + typeof(FieldInfo).GetMethod("SetValue", new[] { typeof(object), typeof(object) })!; + var memberAsObject = adapt.To(typeof(object)); + return Expression.Call(getPropertyExpression, setValueMethod, + new[] { member.Destination, memberAsObject }); + } } } From 0711eb8751696d5b670758afe9027fd1c8ea8086 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sun, 2 Feb 2025 18:18:02 +0500 Subject: [PATCH 16/18] add Test for RecordType check IgnoreNullValue UseDestinationValue Use Destination value from Primitive type AutoProperty --- .../WhenMappingRecordRegression.cs | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs index 2316069b..b1c41f92 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -391,7 +391,36 @@ public void WhenRecordReceivedIgnoreCtorParamProcessing() } + [TestMethod] + public void WhenRecordTypeWorksWithUseDestinationValueAndIgnoreNullValues() + { + + TypeAdapterConfig + .NewConfig() + .IgnoreNullValues(true); + + var _source = new SourceFromTestUseDestValue() { X = 300, Y = 200, Name = new StudentNameRecord() { Name = "John" } }; + var result = _source.Adapt(); + + var _sourceFromMapToTarget = new SourceFromTestUseDestValue() { A = 100, X = null, Y = null, Name = null }; + + var txt1 = _sourceFromMapToTarget.BuildAdapter().CreateMapExpression(); + + var txt = _sourceFromMapToTarget.BuildAdapter().CreateMapToTargetExpression(); + + var _resultMapToTarget = _sourceFromMapToTarget.Adapt(result); + + result.A.ShouldBe(0); // default Value - not match + result.S.ShouldBe("Inside Data"); // is not AutoProperty not mod by source + result.Y.ShouldBe(200); // Y is AutoProperty value transmitted from source + result.Name.Name.ShouldBe("John"); // transmitted from source standart method + _resultMapToTarget.A.ShouldBe(100); + _resultMapToTarget.X.ShouldBe(300); // Ignore NullValues work + _resultMapToTarget.Y.ShouldBe(200); // Ignore NullValues work + _resultMapToTarget.Name.Name.ShouldBe("John"); // Ignore NullValues work + + } #region NowNotWorking @@ -419,6 +448,37 @@ public void CollectionUpdate() #region TestClasses + public class SourceFromTestUseDestValue + { + public int? A { get; set; } + public int? X { get; set; } + public int? Y { get; set; } + public StudentNameRecord Name { get; set; } + } + + + public record TestRecordUseDestValue() + { + private string _s = "Inside Data"; + + public int A { get; set; } + public int X { get; set; } + + [UseDestinationValue] + public int Y { get; } + + [UseDestinationValue] + public string S { get => _s; } + + [UseDestinationValue] + public StudentNameRecord Name { get; } = new StudentNameRecord() { Name = "Marta" }; + } + + public record StudentNameRecord + { + public string Name { get; set; } + } + public record TestRecordY() { public int X { get; set; } @@ -724,14 +784,5 @@ sealed record TestSealedRecord() sealed record TestSealedRecordPositional(int X); - - - - - - - - - #endregion TestClasses } From 892c42f7947cd81d2b72fd9da833e7c6abfc689c Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 3 Feb 2025 11:07:43 +0500 Subject: [PATCH 17/18] fix null Check --- src/Mapster/Adapters/RecordTypeAdapter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 7411c097..66cb38a7 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -194,9 +194,11 @@ protected override Expression CreateBlockExpression(Expression source, Expressio var var2Param = ClassConverterContext.Members.Where(x => x.DestinationMember.Name == member.DestinationMember.Name).FirstOrDefault(); Expression destMemberVar2 = var2Param.DestinationMember.GetExpression(var2Param.Destination); + var ParamLambdaVar2 = destMemberVar2; if(member.DestinationMember.Type.IsRecordType()) - destMemberVar2 = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, destMemberVar2); - var blocksVar2 = Expression.Block(SetValueTypeAutoPropertyByReflection(member, destMemberVar2, classModel)); + ParamLambdaVar2 = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, destMemberVar2); + + var blocksVar2 = Expression.Block(SetValueTypeAutoPropertyByReflection(member, ParamLambdaVar2, classModel)); var lambdaVar2 = Expression.Lambda(blocksVar2, parameters: new[] { (ParameterExpression)var2Param.Destination, (ParameterExpression)destination }); var adaptVar2 = Expression.Invoke(lambdaVar2, var2Param.Destination, destination); From 23a508973a2f31fa1f0dee7a84a577d93a2d3b76 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 3 Feb 2025 19:57:25 +0500 Subject: [PATCH 18/18] fix IgnoreNullValues for RecordType when used Map mapping --- src/Mapster/Adapters/RecordTypeAdapter.cs | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 66cb38a7..81c1193e 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -15,6 +15,8 @@ internal class RecordTypeAdapter : ClassAdapter protected override int Score => -149; protected override bool UseTargetValue => false; + private List SkipIgnoreNullValuesMemberMap = new List(); + protected override bool CanMap(PreCompileArgument arg) { return arg.DestinationType.IsRecordType(); @@ -33,6 +35,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E { //new TDestination(src.Prop1, src.Prop2) + SkipIgnoreNullValuesMemberMap.Clear(); Expression installExpr; if (arg.GetConstructUsing() != null || arg.DestinationType == null) @@ -83,8 +86,14 @@ protected override Expression CreateInstantiationExpression(Expression source, E var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); - if (arg.MapType == MapType.MapToTarget && arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull()) // add IgnoreNullValues support + if (arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull()) // add IgnoreNullValues support { + if (arg.MapType != MapType.MapToTarget) + { + SkipIgnoreNullValuesMemberMap.Add(member); + continue; + } + if (adapt is ConditionalExpression condEx) { if (condEx.Test is BinaryExpression { NodeType: ExpressionType.Equal } binEx && @@ -160,6 +169,29 @@ protected override Expression CreateBlockExpression(Expression source, Expressio var lines = new List(); + if (arg.MapType != MapType.MapToTarget) + { + foreach (var member in SkipIgnoreNullValuesMemberMap) + { + + var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + + if (adapt is ConditionalExpression condEx) + { + if (condEx.Test is BinaryExpression { NodeType: ExpressionType.Equal } binEx && + binEx.Left == member.Getter && + binEx.Right is ConstantExpression { Value: null }) + adapt = condEx.IfFalse; + } + adapt = member.DestinationMember.SetExpression(destination, adapt); + var sourceCondition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); + + + lines.Add(Expression.IfThen(sourceCondition, adapt)); + } + } + + foreach (var member in members) { if (member.DestinationMember.SetterModifier == AccessModifier.None && member.UseDestinationValue)