Description
I try to map from class A to class B, where B is missing a property of A.
Usually, to do this you have to use .Ignore(e => e.Property)
but I use a non-default constructor for initialization of the destination class (because I need to validate the incoming data within the constructor) and this fails.
Apparently, Mapster removes the ignored parameter from the constructor invocation parameters instead of using the default parameter value. And this inevitably fails.
Here is some sample code:
using Mapster;
using System.Reflection;
var config = new TypeAdapterConfig()
{
RequireExplicitMapping = false,
RequireDestinationMemberSource = true,
};
config.Default
.PreserveReference(true)
.MapToConstructor(true)
.Unflattening(false)
.IgnoreNullValues(false)
.NameMatchingStrategy(new NameMatchingStrategy
{
SourceMemberNameConverter = input => input.ToLowerInvariant(),
DestinationMemberNameConverter = input => input.ToLowerInvariant(),
})
.ShallowCopyForSameType(false);
var ctorToUse = GetConstructor<B>();
config
.NewConfig<A, B>()
.Ignore(e => e.Id) // <--------------- The code fails with or without this invocation.
.MapToConstructor(ctorToUse);
var mapper = new MapsterMapper.Mapper(config);
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;
}
var a = new A
{
Text = "test",
};
var docKind = mapper.Map<B>(a); // <------ This fails.
class A
{
public string? Text { get; set; }
}
class B
{
public int Id { get; private set; }
public string Text { get; private set; }
public B(int id, string text)
{
Id = id;
Text = text;
}
}
My humble opinion is that this code should work and invoke the constructor using the default value for Id
, which is default(int)
, which is 0
. Omitting it during the invocation, which is what currently appears to happen, makes little sense to me.
I would also fancy an .Ignore()
overload method that allows providing the value for the property, like this one:
config
.NewConfig<A, B>()
.Ignore(e => e.Id, 0) // Use value of 0 for id
.Ignore(e => e.Id2, 0) // Use value of 0 for id2
.MapToConstructor(ctorToUse);
...but perhaps this is too much to ask.
P.S. :
I know I could use .MapWith(a => new B(0, a.Text))
to manually fix the issue, but I try to keep the mapping as automated as possible, because I need to do this configuration for many types that contain a lot of constructor parameters and this would be messy. So, this is not an option for my case.