diff --git a/src/Mapster.Tests/WhenIgnoreMapping.cs b/src/Mapster.Tests/WhenIgnoreMapping.cs index 585e911c..245c4e63 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,109 @@ 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"); + } + + /// + /// 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]); + + 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 +172,7 @@ public class Dto [JsonIgnore] public string Name { get; set; } } + + #endregion TestClasses } } 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 e5d719e4..6304e4a0 100644 --- a/src/Mapster.Tests/WhenMappingRecordRegression.cs +++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs @@ -2,6 +2,7 @@ using Shouldly; using System; using System.Collections.Generic; +using static Mapster.Tests.WhenMappingDerived; namespace Mapster.Tests { @@ -14,11 +15,20 @@ public class WhenMappingRecordRegression [TestMethod] public void AdaptRecordToRecord() { + TypeAdapterConfig + .NewConfig() + .Ignore(dest => dest.Y); + var _source = new TestRecord() { X = 700 }; - var _destination = new TestRecord() { X = 500 }; + var _destination = new TestRecordY() { X = 500 , Y = 200 }; + + var _destination2 = new TestRecordY() { X = 300, Y = 400 }; var _result = _source.Adapt(_destination); + var result2 = _destination.Adapt(_destination2); + _result.X.ShouldBe(700); + _result.Y.ShouldBe(200); object.ReferenceEquals(_result, _destination).ShouldBeFalse(); } @@ -353,7 +363,85 @@ public void MappingInterfaceToInterface() } + /// + /// https://github.com/MapsterMapper/Mapster/issues/456 + /// + [TestMethod] + 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 DtoInsider = new DtoInside(userDto); + var UserInsider = new UserInside(user, new UserRecord456("Skot")); + + var map = userDto.Adapt(); + var maptoTarget = userDto.Adapt(user); + + var MapToTargetInsider = DtoInsider.Adapt(UserInsider); + + 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 + + } + + [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 + + } + + /// + /// https://github.com/MapsterMapper/Mapster/issues/771 + /// https://github.com/MapsterMapper/Mapster/issues/746 + /// + [TestMethod] + public void FixCtorParamMapping() + { + var sourceRequestPaymentDto = new PaymentDTO771("MasterCard", "1234", "12/99", "234", 12); + var sourceRequestOrderDto = new OrderDTO771(Guid.NewGuid(), Guid.NewGuid(), "order123", sourceRequestPaymentDto); + var db = new Database746(UserID: "256", Password: "123"); + + + var result = new CreateOrderRequest771(sourceRequestOrderDto).Adapt(); + var resultID = db.Adapt(new Database746()); + + + result.Order.Payment.CVV.ShouldBe("234"); + resultID.UserID.ShouldBe("256"); + } #region NowNotWorking @@ -382,6 +470,77 @@ public void CollectionUpdate() #region TestClasses + public sealed record Database746( + string Server = "", + string Name = "", + string? UserID = null, + string? Password = null); + + public record CreateOrderRequest771(OrderDTO771 Order); + + public record CreateOrderCommand771(OrderDTO771 Order); + + + public record OrderDTO771 + ( + Guid Id, + Guid CustomerId, + string OrderName, + PaymentDTO771 Payment + ); + + public record PaymentDTO771 + ( + string CardName, + string CardNumber, + string Expiration, + string CVV, + int PaymentMethod + ); + 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; } + 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); + public interface IActivityDataExtentions : IActivityData { public int TempLength { get; set; } @@ -674,14 +833,5 @@ sealed record TestSealedRecord() sealed record TestSealedRecordPositional(int X); - - - - - - - - - #endregion TestClasses } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index faa490ec..35ad6548 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,11 +15,12 @@ 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, ClassModel recordRestorMemberModel = null) { var destinationMembers = classModel.Members; var unmappedDestinationMembers = new List(); var properties = new List(); + arg.ConstructorMapping = ctorMapping; var sources = new List {source}; sources.AddRange( @@ -29,7 +30,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(); @@ -54,6 +55,10 @@ 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()) + { + getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination) ?? getter; + } if (getter != null) { propertyModel.Getter = arg.MapType == MapType.Projection @@ -80,7 +85,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 +134,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, ClassModel recordRestorParamModel = null) { var members = classConverter.Members; @@ -144,6 +150,9 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi if (member.Getter == null) { getter = defaultConst; + + if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) + getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination) ?? getter; } else { @@ -156,6 +165,14 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi var condition = ExpressionEx.Not(body); getter = Expression.Condition(condition, getter, defaultConst); } + else + if (arg.Settings.Ignore.Any(x => x.Key == member.DestinationMember.Name)) + { + getter = defaultConst; + + if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) + getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination) ?? getter; + } } arguments.Add(getter); } @@ -181,6 +198,24 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) }; } + protected Expression? 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) + { + 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; + } + #endregion } } diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 407c5a5b..6ae450fa 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..ea4aa6e1 100644 --- a/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs +++ b/src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs @@ -38,8 +38,8 @@ 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); - return CreateInstantiationExpression(source, classConverter, arg); + var classConverter = CreateClassConverter(source, classModel, arg, ctorMapping:true); + 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..81c1193e 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -1,52 +1,60 @@ -using System.Collections.Generic; +using Mapster.Models; +using Mapster.Utils; +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 { internal class RecordTypeAdapter : ClassAdapter { + private ClassMapping? ClassConverterContext; 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(); } - protected override Expression CreateInstantiationExpression(Expression source, Expression? destination, CompileArgument arg) - { - //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); - var installExpr = CreateInstantiationExpression(source, classConverter, arg); - return RecordInlineExpression(source, arg, installExpr); // Activator field when not include in public ctor - } - - protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) + protected override bool CanInline(Expression source, Expression? destination, CompileArgument arg) { - return Expression.Empty(); + 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) + + SkipIgnoreNullValuesMemberMap.Clear(); + 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 restorParamModel = GetSetterModel(arg); + var classConverter = CreateClassConverter(source, classModel, arg, ctorMapping: true); + installExpr = CreateInstantiationExpression(source, classConverter, arg, destination, restorParamModel); + } + - private Expression? RecordInlineExpression(Expression source, CompileArgument arg, Expression installExpr) + 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) { //new TDestination { // Prop1 = convert(src.Prop1), @@ -56,27 +64,51 @@ 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 = newInstance.Constructor?.GetParameters().ToList() ?? new(); var classModel = GetSetterModel(arg); - var classConverter = CreateClassConverter(source, classModel, arg); + 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) { - if (member.UseDestinationValue) - return null; if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) - && member.Getter is MemberExpression memberExp && contructorMembers.Contains(memberExp.Member)) + && contructorMembers.Any(x => string.Equals(x.Name, member.DestinationMember.Name, StringComparison.InvariantCultureIgnoreCase))) continue; if (member.DestinationMember.SetterModifier == AccessModifier.None) continue; - var value = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member); + + 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 && + 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 @@ -87,14 +119,196 @@ 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) + lines.AddRange(RecordIngnoredWithoutConditonRestore(destination, arg, contructorMembers, classModel)); + return Expression.MemberInit(newInstance, lines); } + + 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 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; + } + + 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(); + + 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) + { + + 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); + var ParamLambdaVar2 = destMemberVar2; + if(member.DestinationMember.Type.IsRecordType()) + 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); + + + 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 }); + } } } diff --git a/src/Mapster/Compile/CompileArgument.cs b/src/Mapster/Compile/CompileArgument.cs index 2e15637a..23c540ea 100644 --- a/src/Mapster/Compile/CompileArgument.cs +++ b/src/Mapster/Compile/CompileArgument.cs @@ -15,6 +15,7 @@ public class CompileArgument public TypeAdapterSettings Settings { get; set; } public CompileContext Context { get; set; } public bool UseDestinationValue { get; set; } + public bool? ConstructorMapping { get; set; } private HashSet? _srcNames; internal HashSet GetSourceNames() diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index ea47d9ad..fd13407d 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -73,10 +73,10 @@ public static class ValueAccessingStrategy { var members = source.Type.GetFieldsAndProperties(true); var strategy = arg.Settings.NameMatchingStrategy; - var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); + var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); return members .Where(member => member.ShouldMapMember(arg, MemberSide.Source)) - .Where(member => member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter) == destinationMemberName) + .Where(member => member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter, arg) == destinationMemberName) .Select(member => member.GetExpression(source)) .FirstOrDefault(); } @@ -86,7 +86,7 @@ public static class ValueAccessingStrategy if (arg.MapType == MapType.Projection) return null; var strategy = arg.Settings.NameMatchingStrategy; - var destinationMemberName = "Get" + destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); + var destinationMemberName = "Get" + destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); var getMethod = Array.Find(source.Type.GetMethods(BindingFlags.Public | BindingFlags.Instance), m => strategy.SourceMemberNameConverter(m.Name) == destinationMemberName && m.GetParameters().Length == 0); if (getMethod == null) return null; @@ -98,7 +98,7 @@ public static class ValueAccessingStrategy private static Expression? FlattenMemberFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; - var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); + var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); return GetDeepFlattening(source, destinationMemberName, arg); } @@ -111,7 +111,7 @@ public static class ValueAccessingStrategy if (!member.ShouldMapMember(arg, MemberSide.Source)) continue; - var sourceMemberName = member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter); + var sourceMemberName = member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter, arg); if (string.Equals(propertyName, sourceMemberName)) return member.GetExpression(source); @@ -132,14 +132,14 @@ public static class ValueAccessingStrategy internal static IEnumerable FindUnflatteningPairs(Expression source, IMemberModel destinationMember, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; - var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); + var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); var members = source.Type.GetFieldsAndProperties(true); foreach (var member in members) { if (!member.ShouldMapMember(arg, MemberSide.Source)) continue; - var sourceMemberName = member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter); + var sourceMemberName = member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter, arg); if (!sourceMemberName.StartsWith(destinationMemberName) || sourceMemberName == destinationMemberName) continue; foreach (var prop in GetDeepUnflattening(destinationMember, sourceMemberName.Substring(destinationMemberName.Length).TrimStart('_'), arg)) @@ -161,7 +161,7 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM { if (!member.ShouldMapMember(arg, MemberSide.Destination)) continue; - var destMemberName = member.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); + var destMemberName = member.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); var propertyType = member.Type; if (string.Equals(propertyName, destMemberName)) { @@ -185,7 +185,7 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM return null; var strategy = arg.Settings.NameMatchingStrategy; - var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); + var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); var key = Expression.Constant(destinationMemberName); var args = dictType.GetGenericArguments(); if (strategy.SourceMemberNameConverter != MapsterHelper.Identity) diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index dae413d8..40098632 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -311,11 +311,14 @@ public static bool UseDestinationValue(this IMemberModel member, CompileArgument return predicates.Any(predicate => predicate(member)); } - public static string GetMemberName(this IMemberModel member, MemberSide side, List> getMemberNames, Func nameConverter) + public static string GetMemberName(this IMemberModel member, MemberSide side, List> getMemberNames, Func nameConverter, CompileArgument arg) { var memberName = getMemberNames.Select(func => func(member, side)) - .FirstOrDefault(name => name != null) - ?? member.Name; + .FirstOrDefault(name => name != null); + if (memberName == null && arg.ConstructorMapping == true) + memberName = member.Name.ToPascalCase(); + if (memberName == null) + memberName = member.Name; return nameConverter(memberName); }