Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to Ignore() processing and Fix MapToTarget behavior for RecordTypes #769

Open
wants to merge 19 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 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
107 changes: 107 additions & 0 deletions src/Mapster.Tests/WhenIgnoreMapping.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Shouldly;
Expand Down Expand Up @@ -55,6 +57,109 @@ public void TestIgnoreMember()
poco2.Name.ShouldBeNull();
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/707
/// </summary>
[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<A707, B707>()
.MapToConstructor(GetConstructor<B707>())
.Ignore(e => e.Id);

var source = new A707 { Text = "test" };
var dest = new B707(123, "Hello");

var docKind = source.Adapt<B707>(config);
var mapTotarget = source.Adapt(dest,config);

docKind.Id.ShouldBe(0);
mapTotarget.Id.ShouldBe(123);
mapTotarget.Text.ShouldBe("test");
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/723
/// </summary>
[TestMethod]
public void MappingToIntefaceWithIgnorePrivateSetProperty()
{
TypeAdapterConfig<InterfaceSource723, InterfaceDestination723>
.NewConfig()
.TwoWays()
.Ignore(dest => dest.Ignore);

InterfaceDestination723 dataDestination = new Data723() { Inter = "IterDataDestination", Ignore = "IgnoreDataDestination" };

Should.NotThrow(() =>
{
var isourse = dataDestination.Adapt<InterfaceSource723>();
var idestination = dataDestination.Adapt<InterfaceDestination723>();
});

}

#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<TDestination>()
{
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; }
Expand All @@ -67,5 +172,7 @@ public class Dto
[JsonIgnore]
public string Name { get; set; }
}

#endregion TestClasses
}
}
6 changes: 4 additions & 2 deletions src/Mapster.Tests/WhenIgnoringConditionally.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public void IgnoreIf_Apply_To_RecordType()
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };

var srt = poco.BuildAdapter().CreateMapToTargetExpression<SimpleRecord>();
var dto = TypeAdapter.Adapt<SimplePoco, SimpleRecord>(poco);

dto.Id.ShouldBe(1);
Expand All @@ -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)
{
Expand Down
52 changes: 51 additions & 1 deletion src/Mapster.Tests/WhenMappingRecordRegression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@ public class WhenMappingRecordRegression
[TestMethod]
public void AdaptRecordToRecord()
{
TypeAdapterConfig<TestRecordY, TestRecordY>
.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();
}

Expand Down Expand Up @@ -353,6 +362,35 @@ public void MappingInterfaceToInterface()

}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/456
/// </summary>
[TestMethod]
public void WhenRecordReceivedIgnoreCtorParamProcessing()
{
TypeAdapterConfig<UserDto456,UserRecord456>.NewConfig()
.Ignore(dest => dest.Name);

TypeAdapterConfig<DtoInside, UserInside>.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<UserRecord456>();
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

}



#region NowNotWorking
Expand Down Expand Up @@ -381,6 +419,18 @@ public void CollectionUpdate()


#region TestClasses
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
{
Expand Down
41 changes: 37 additions & 4 deletions src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal abstract class BaseClassAdapter : BaseAdapter

#region Build the Adapter Model

protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null)
protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg, Expression? destination = null, bool ctorMapping = false)
{
var destinationMembers = classModel.Members;
var unmappedDestinationMembers = new List<string>();
Expand All @@ -29,7 +29,7 @@ src is LambdaExpression lambda
: ExpressionEx.PropertyOrFieldPath(source, (string)src)));
foreach (var destinationMember in destinationMembers)
{
if (ProcessIgnores(arg, destinationMember, out var ignore))
if (ProcessIgnores(arg, destinationMember, out var ignore) && !ctorMapping)
continue;

var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable();
Expand All @@ -54,6 +54,15 @@ select fn(src, destinationMember, arg))
Destination = (ParameterExpression?)destination,
UseDestinationValue = arg.MapType != MapType.Projection && destinationMember.UseDestinationValue(arg),
};
if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType())
{
var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray()
.Where(x => x.Name == destinationMember.Name).FirstOrDefault();

DocSvartz marked this conversation as resolved.
Show resolved Hide resolved
if (find != null && destination != null)
getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info);

}
if (getter != null)
{
propertyModel.Getter = arg.MapType == MapType.Projection
Expand All @@ -80,7 +89,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);
Expand Down Expand Up @@ -128,7 +138,7 @@ protected static bool ProcessIgnores(
&& ignore.Condition == null;
}

protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg)
protected Expression CreateInstantiationExpression(Expression source, ClassMapping classConverter, CompileArgument arg, Expression? destination)
{
var members = classConverter.Members;

Expand All @@ -144,6 +154,15 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
if (member.Getter == null)
{
getter = defaultConst;

if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType())
{
var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray()
.Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault();

if (find != null && destination != null)
DocSvartz marked this conversation as resolved.
Show resolved Hide resolved
getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info);
}
}
else
{
Expand All @@ -156,6 +175,20 @@ 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())
{
var find = arg.DestinationType.GetFieldsAndProperties(arg.Settings.EnableNonPublicMembers.GetValueOrDefault()).ToArray()
DocSvartz marked this conversation as resolved.
Show resolved Hide resolved
.Where(x => x.Name == member.DestinationMember.Name).FirstOrDefault();

if (find != null)
getter = Expression.MakeMemberAccess(destination, (MemberInfo)find.Info);
}
}
}
arguments.Add(getter);
}
Expand Down
6 changes: 3 additions & 3 deletions src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/Mapster/Adapters/ReadOnlyInterfaceAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading