Source/destination types
// Source
public record AuthorRequest(
string Name,
string Biography,
DateTime DateOfBirth);
// Destination
public record AuthorUpdateCommand(
Guid Id,
string Name,
string Biography,
DateTime DateOfBirth
) : AuthorCommand(Name, Biography, DateOfBirth), IRequest<Result<Updated>>;
Mapping configuration
public class AuthorProfile : Profile
{
public AuthorProfile()
{
CreateMap<(Guid Id, AuthorRequest AuthorRequest), AuthorUpdateCommand>()
.ForMember(dst => dst, opt => opt.MapFrom(src => src.AuthorRequest))
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id));
}
}
Version: 13.0.1
Expected behavior
I would like to do something similar that I do with the Mapster library
to create a projection between a set of tuples and convert it to AuthorUpdateCommand
. For example, I want something that, when I add a new field to AuthorRequest and AuthorUpdateCommand, I don’t need to modify the mapping anymore.
Actual behavior
Exception has occurred: CLR/System.ArgumentException
An exception of type 'System.ArgumentException' occurred in AutoMapper.dll but was not handled in user code: 'Expression 'dst => dst' must resolve to top-level member and not any child object's properties. You can use ForPath, a custom resolver on the child type or the AfterMap option instead.'
at AutoMapper.Internal.ReflectionHelper.FindProperty(LambdaExpression lambdaExpression)
at AutoMapper.Configuration.MappingExpression`2.ForMember[TMember](Expression`1 destinationMember, Action`1 memberOptions)
at CatalogContext.Application.Common.Mappings.AuthorProfile..ctor() in c:UsersjoseMusicgithubProjetosBookVerseCatalogContextsrcCatalogContext.ApplicationCommonMappingsAuthorProfile.cs:line 16
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
Steps to reproduce
I make a request to the controller. Just like I defined in the mapping, I am passing a tuple with Id
and AuthorRequest
. AuthorRequest
has almost the same fields, except for the Id. That’s why I am trying to create a projection.
public record AuthorRequest(
string Name,
string Biography,
DateTime DateOfBirth);
[HttpPut("{id:guid}")]
public async Task<IActionResult> Update(Guid id, AuthorRequest request)
{
var command = _mapper.Map<AuthorUpdateCommand>((id, request));
var result = await _mediator.Send(command);
if (result.IsError)
{
return BadRequest();
}
return Ok(result.Value);
}
After that, it returns this exception exactly as described in the Actual behavior
section.
2
Answers
At first I thought that this answer could help you, but your requirement has the additional challenge of the target
AuthorUpdateCommand
record not having a default/parameterless constructor.The solution in the linked answer would work in case
AuthorUpdateCommand
would have a default constructor, but fails here on instantiating anAuthorUpdateCommand
because of the tuple member names not matching the constructor parameter names.One way to solve all this is by using
ForCtorParam
as shown below.As an alternative — and following up on the comments below — you can use
IncludeMembers
as shown in above linked answer, but the target models/records need a default constructor and an additional mapping;CreateMap<AuthorRequest, AuthorUpdateCommand>()
.Working code with constructors: