diff --git a/src/Mapster.Tests/WhenMappingRecordRegression.cs b/src/Mapster.Tests/WhenMappingRecordRegression.cs
index bdb9b833..fe79b47b 100644
--- a/src/Mapster.Tests/WhenMappingRecordRegression.cs
+++ b/src/Mapster.Tests/WhenMappingRecordRegression.cs
@@ -477,6 +477,7 @@ public void ClassCtorAutomapingWorking()
///
/// https://github.com/MapsterMapper/Mapster/issues/842
///
+ [Ignore] // after fix https://github.com/MapsterMapper/Mapster/issues/883
[TestMethod]
public void ClassCustomCtorWitoutMapNotWorking()
{
@@ -537,6 +538,24 @@ public void ClassUpdateAutoPropertyWitoutSetterWorking()
result.X.ShouldBe(200);
}
+ ///
+ /// https://github.com/MapsterMapper/Mapster/issues/883
+ ///
+ [TestMethod]
+ public void ClassCtorActivateDefaultValue()
+ {
+ var source = new Source833
+ {
+ Value1 = "123",
+ };
+
+ Should.NotThrow(() =>
+ {
+ var target = source.Adapt();
+ target.Value1.ShouldBe("123");
+ target.Value2.ShouldBe(default);
+ });
+ }
#region NowNotWorking
@@ -974,5 +993,23 @@ class InsiderWithCtorDestYx
public AutoCtorDestYx X { set; get; }
}
+ public class Source833
+ {
+ public required string Value1 { get; init; }
+ }
+
+ public class Target833
+ {
+ public Target833(string value1, string value2)
+ {
+ Value1 = value1;
+ Value2 = value2;
+ }
+
+ public string Value1 { get; }
+
+ public string Value2 { get; }
+ }
+
#endregion TestClasses
}
diff --git a/src/Mapster.Tests/WhenUsingNonDefaultConstructor.cs b/src/Mapster.Tests/WhenUsingNonDefaultConstructor.cs
index ac41e5fc..45bea2d5 100644
--- a/src/Mapster.Tests/WhenUsingNonDefaultConstructor.cs
+++ b/src/Mapster.Tests/WhenUsingNonDefaultConstructor.cs
@@ -72,6 +72,7 @@ public void Map_To_Existing_Destination_Instance_Should_Pass()
dto.Unmapped.ShouldBe("unmapped");
}
+ [Ignore] // after https://github.com/MapsterMapper/Mapster/issues/883
[TestMethod]
public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Exception()
{
diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs
index 41208035..b0155ba8 100644
--- a/src/Mapster/Adapters/BaseClassAdapter.cs
+++ b/src/Mapster/Adapters/BaseClassAdapter.cs
@@ -40,8 +40,8 @@ src is LambdaExpression lambda
if (arg.Settings.IgnoreNonMapped == true)
resolvers = resolvers.Where(ValueAccessingStrategy.CustomResolvers.Contains);
var getter = (from fn in resolvers
- from src in sources
- select fn(src, destinationMember, arg))
+ from src in sources
+ select fn(src, destinationMember, arg))
.FirstOrDefault(result => result != null);
if (arg.MapType == MapType.Projection && getter != null)
@@ -111,9 +111,9 @@ select fn(src, destinationMember, arg))
NextIgnore = nextIgnore,
Source = (ParameterExpression)source,
Destination = (ParameterExpression?)destination,
- UseDestinationValue = arg.MapType != MapType.Projection && destinationMember.UseDestinationValue(arg),
+ UseDestinationValue = IsCanUsingDestinationValue(arg, destinationMember),
};
- if(getter == null && !arg.DestinationType.IsRecordType()
+ if (getter == null && !arg.DestinationType.IsRecordType(arg)
&& destinationMember.Info is PropertyInfo propinfo)
{
if (propinfo.GetCustomAttributes()
@@ -123,14 +123,14 @@ select fn(src, destinationMember, arg))
}
}
- if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType())
+ if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType(arg))
{
getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination) ?? getter;
}
if (getter != null)
{
- propertyModel.Getter = arg.MapType == MapType.Projection
- ? getter
+ propertyModel.Getter = arg.MapType == MapType.Projection
+ ? getter
: getter.ApplyNullPropagation();
properties.Add(propertyModel);
}
@@ -189,6 +189,16 @@ select fn(src, destinationMember, arg))
};
}
+ protected static bool IsCanUsingDestinationValue(CompileArgument arg, IMemberModelEx destinationMember)
+ {
+ if (arg.MapType == MapType.Projection)
+ return false;
+ if(destinationMember.UseDestinationValue(arg) || arg.Settings.UseDestinationMember.Contains(destinationMember.Name))
+ return true;
+
+ return false;
+ }
+
protected static bool ProcessIgnores(
CompileArgument arg,
IMemberModel destinationMember,
@@ -219,7 +229,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
{
getter = defaultConst;
- if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType())
+ if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType(arg))
getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination) ?? getter;
}
else
@@ -249,7 +259,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
{
getter = defaultConst;
- if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType())
+ if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType(arg))
getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination) ?? getter;
}
}
diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs
index 9e44105d..f3fe2720 100644
--- a/src/Mapster/Adapters/ClassAdapter.cs
+++ b/src/Mapster/Adapters/ClassAdapter.cs
@@ -66,11 +66,16 @@ protected override Expression CreateInstantiationExpression(Expression source, E
: arg.DestinationType;
if (destType == null)
return base.CreateInstantiationExpression(source, destination, arg);
- classConverter = destType.GetConstructors()
+
+ var constructors = destType.GetConstructors();
+ classConverter = constructors
.OrderByDescending(it => it.GetParameters().Length)
.Select(it => GetConstructorModel(it, true))
.Select(it => CreateClassConverter(source, it, arg, ctorMapping:true))
.FirstOrDefault(it => it != null);
+
+ if(classConverter == null && constructors.Length > 0)
+ classConverter = CreateClassConverter(source, GetConstructorModel(constructors[0], false), arg, ctorMapping: true);
}
else
{
diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs
index a4057111..6ae30b18 100644
--- a/src/Mapster/Adapters/RecordTypeAdapter.cs
+++ b/src/Mapster/Adapters/RecordTypeAdapter.cs
@@ -19,7 +19,12 @@ internal class RecordTypeAdapter : ClassAdapter
protected override bool CanMap(PreCompileArgument arg)
{
- return arg.DestinationType.IsRecordType() && arg.MapType != MapType.Projection;
+ if(arg.MapType == MapType.Projection)
+ return false;
+ if (arg.CustomRecordType)
+ return true;
+
+ return arg.DestinationType.IsRecordType(arg);
}
protected override bool CanInline(Expression source, Expression? destination, CompileArgument arg)
@@ -199,11 +204,11 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
if (member.DestinationMember is PropertyModel && member.DestinationMember.Type.IsValueType
|| member.DestinationMember.Type.IsMapsterPrimitive()
- || member.DestinationMember.Type.IsRecordType())
+ || member.DestinationMember.Type.IsRecordType(arg))
{
Expression adapt;
- if (member.DestinationMember.Type.IsRecordType())
+ if (member.DestinationMember.Type.IsRecordType(arg))
adapt = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, member.Getter);
else
adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, result);
@@ -227,7 +232,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
Expression destMemberVar2 = var2Param.DestinationMember.GetExpression(var2Param.Destination);
var ParamLambdaVar2 = destMemberVar2;
- if(member.DestinationMember.Type.IsRecordType())
+ if(member.DestinationMember.Type.IsRecordType(arg))
ParamLambdaVar2 = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, destMemberVar2);
var blocksVar2 = Expression.Block(SetValueTypeAutoPropertyByReflection(member, ParamLambdaVar2, classModel));
diff --git a/src/Mapster/Compile/PreCompileArgument.cs b/src/Mapster/Compile/PreCompileArgument.cs
index a907d267..df5c975e 100644
--- a/src/Mapster/Compile/PreCompileArgument.cs
+++ b/src/Mapster/Compile/PreCompileArgument.cs
@@ -9,5 +9,6 @@ public class PreCompileArgument
public Type DestinationType;
public MapType MapType;
public bool ExplicitMapping;
+ public bool CustomRecordType;
}
}
diff --git a/src/Mapster/Models/TypeTuple.cs b/src/Mapster/Models/TypeTuple.cs
index e25a71fd..32f5cd04 100644
--- a/src/Mapster/Models/TypeTuple.cs
+++ b/src/Mapster/Models/TypeTuple.cs
@@ -39,5 +39,15 @@ public TypeTuple(Type source, Type destination)
Source = source;
Destination = destination;
}
+
+ public static TypeTuple ForDestinationType(Type destination)
+ {
+ return new TypeTuple(typeof(void), destination);
+ }
+
+ public static TypeTuple ForDestinationType(TypeTuple tuple)
+ {
+ return new TypeTuple(typeof(void), tuple.Destination);
+ }
}
}
diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs
index fd13407d..df02b158 100644
--- a/src/Mapster/Settings/ValueAccessingStrategy.cs
+++ b/src/Mapster/Settings/ValueAccessingStrategy.cs
@@ -117,7 +117,7 @@ public static class ValueAccessingStrategy
var propertyType = member.Type;
if (propertyName.StartsWith(sourceMemberName) &&
- (propertyType.IsPoco() || propertyType.IsRecordType()))
+ (propertyType.IsPoco() || propertyType.IsRecordType(arg)))
{
var exp = member.GetExpression(source);
var ifTrue = GetDeepFlattening(exp, propertyName.Substring(sourceMemberName.Length).TrimStart('_'), arg);
@@ -168,7 +168,7 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM
yield return member.Name;
}
else if (propertyName.StartsWith(destMemberName) &&
- (propertyType.IsPoco() || propertyType.IsRecordType()))
+ (propertyType.IsPoco() || propertyType.IsRecordType(arg)))
{
foreach (var prop in GetDeepUnflattening(member, propertyName.Substring(destMemberName.Length).TrimStart('_'), arg))
{
diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs
index 1e6abade..3a71e05a 100644
--- a/src/Mapster/TypeAdapterConfig.cs
+++ b/src/Mapster/TypeAdapterConfig.cs
@@ -201,7 +201,7 @@ public TypeAdapterSetter ForType(Type sourceType, Type destinationType)
///
public TypeAdapterSetter ForDestinationType()
{
- var key = new TypeTuple(typeof(void), typeof(TDestination));
+ var key = TypeTuple.ForDestinationType(typeof(TDestination));
var settings = GetSettings(key);
return new TypeAdapterSetter(settings, this);
}
@@ -214,7 +214,7 @@ public TypeAdapterSetter ForDestinationType()
///
public TypeAdapterSetter ForDestinationType(Type destinationType)
{
- var key = new TypeTuple(typeof(void), destinationType);
+ var key = TypeTuple.ForDestinationType(destinationType);
var settings = GetSettings(key);
return new TypeAdapterSetter(settings, this);
}
@@ -593,6 +593,7 @@ internal TypeAdapterSettings GetMergedSettings(TypeTuple tuple, MapType mapType)
DestinationType = tuple.Destination,
MapType = mapType,
ExplicitMapping = RuleMap.ContainsKey(tuple),
+ CustomRecordType = GetSettings(TypeTuple.ForDestinationType(tuple)).DestinationAsRecord.GetValueOrDefault(),
};
//auto add setting if there is attr setting
diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs
index e08ef312..677c46cf 100644
--- a/src/Mapster/TypeAdapterSetter.cs
+++ b/src/Mapster/TypeAdapterSetter.cs
@@ -510,6 +510,36 @@ public TypeAdapterSetter AfterMappingInline(Expression lambda);
return this;
}
+
+ public TypeAdapterSetter DestinationAsRecord(bool value)
+ {
+ this.CheckCompiled();
+
+ Settings.DestinationAsRecord = value;
+ return this;
+ }
+
+ public TypeAdapterSetter UseDestinationValue (Expression> destinationMember)
+ {
+ this.CheckCompiled();
+ var memberName = destinationMember.GetMemberPath()!;
+
+ if (memberName != null)
+ {
+ Settings.UseDestinationMember.Add(memberName);
+ }
+
+ return this;
+ }
+
+ public TypeAdapterSetter UseDestinationValue(string destinationMemberName)
+ {
+ this.CheckCompiled();
+
+ Settings.UseDestinationMember.Add(destinationMemberName);
+
+ return this;
+ }
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S4136:Method overloads should be grouped together", Justification = "")]
diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs
index 15c666a2..d9f75d3a 100644
--- a/src/Mapster/TypeAdapterSettings.cs
+++ b/src/Mapster/TypeAdapterSettings.cs
@@ -185,6 +185,17 @@ public Action? Fork
set => Set(nameof(Fork), value);
}
+ public bool? DestinationAsRecord
+ {
+ get => Get(nameof(PreserveReference));
+ set => Set(nameof(PreserveReference), value);
+ }
+
+ public List UseDestinationMember
+ {
+ get => Get(nameof(UseDestinationMember), () => new List());
+ }
+
internal bool Compiled { get; set; }
public TypeAdapterSettings Clone()
diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs
index 3b9b1a1b..377f5938 100644
--- a/src/Mapster/Utils/ReflectionUtils.cs
+++ b/src/Mapster/Utils/ReflectionUtils.cs
@@ -193,7 +193,17 @@ public static Type UnwrapNullable(this Type type)
return type.IsNullable() ? type.GetGenericArguments()[0] : type;
}
- public static bool IsRecordType(this Type type)
+ public static bool IsRecordType(this Type type, CompileArgument arg)
+ {
+ return arg.Settings.DestinationAsRecord.GetValueOrDefault() || type.IsRecordType();
+ }
+
+ public static bool IsRecordType(this Type type, PreCompileArgument arg)
+ {
+ return arg.CustomRecordType || type.IsRecordType();
+ }
+
+ private static bool IsRecordType(this Type type)
{
//not nullable
if (type.IsNullable())