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);
}