Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper: Problem with mapping records type

Tags:

c#

automapper

I am mapping with automapper 10.1.1 in c# 9 from this class

public record BFrom 
{
    public Guid Id { get; init; }
    public Guid DbExtraId { get; init; }
}

into this

public record ATo(Guid Id, Guid ExtraId) : BaseTo(Id);

And have the following configuration

CreateMap<BFrom, ATo>()
    .ForMember("ExtraId", opt => opt.MapFrom(src => src.DbExtraId))
    .ReverseMap();

But I have a problem when I am trying to map it throws an exception with a message that needs to have a constructor with 0 args or only optional args. Is it possible to fix this problem without changing the record types?

like image 411
ifelseelif Avatar asked Dec 20 '20 16:12

ifelseelif


3 Answers

What you need, is to specify the constructor-parameter by name in your mapping profile like so public AToProfile() => CreateMap<BFrom, ATo>().ForCtorParam(ctorParamName: "ExtraId", m => m.MapFrom(s => s.DbExtraId)).ReverseMap(); That way, AutoMapper will know, from where the value should be taken and will map accordingly.

public class StackoverflowQuestionTest
{
    [Fact]
    public void BFrom_MapTo_ATo()
    {
        // Arrange
        IConfigurationProvider configuration = new MapperConfiguration(cfg => cfg.AddProfile<AToProfile>());
        var source = new BFrom {Id = Guid.NewGuid()};

        // Act
        var target = new Mapper(configuration).Map<ATo>(source);

        // Assert
        target.Id.Should().Be(source.Id);
    }


}

public record BaseTo (Guid Id); // this is an assumption, as you did not specify BaseTo in you question

public record BFrom
{
    public Guid Id { get; init; }
    public Guid DbExtraId { get; init; }
}

public record ATo(Guid Id, Guid ExtraId) : BaseTo(Id);

public class AToProfile : Profile
{
    public AToProfile() =>
        CreateMap<BFrom, ATo>()
            .ForCtorParam(ctorParamName: "ExtraId", m => m.MapFrom(s => s.DbExtraId))
            .ReverseMap();
}
like image 31
froeschli Avatar answered Sep 19 '22 19:09

froeschli


I had the same issue, and ended up creating this extension method to solve it:

public static class AutoMapperExtensions
{
    public static IMappingExpression<TSource, TDestination> MapRecordMember<TSource, TDestination, TMember>(
        this IMappingExpression<TSource, TDestination> mappingExpression,
        Expression<Func<TDestination, TMember>> destinationMember, Expression<Func<TSource, TMember>> sourceMember)
    {
        var memberInfo = ReflectionHelper.FindProperty(destinationMember);
        string memberName = memberInfo.Name;
        return mappingExpression
            .ForMember(destinationMember, opt => opt.MapFrom(sourceMember))
            .ForCtorParam(memberName, opt => opt.MapFrom(sourceMember));
    }
}

Then you simply use it like this:

CreateMap<BFrom, ATo>()
    .MapRecordMember(a => a.ExtraId, src => src.DbExtraId)
    .ReverseMap();

and it will take care of registering both constructor and member so that you don't run into issues like these.

like image 199
Christian Genne Avatar answered Sep 22 '22 19:09

Christian Genne


Try

CreateMap<BFrom, ATo>().DisableCtorValidation()
    .ForMember("ExtraId", opt => opt.MapFrom(src => src.DbExtraId))
    .ReverseMap()
like image 45
Athanasios Kataras Avatar answered Sep 18 '22 19:09

Athanasios Kataras