AutoMapper/AutoMapper.Extensions.ExpressionMapping

Mapping MemberInit expressions fail for custom MapFrom expressions.

Apolloangel opened this issue · 3 comments

The issue I'm currently facing is that I have a Select expression based on a model (DTO) where some of the selected properties are part of a SubObject. when I try to map this expression to an expression to another model (DB). Here are the classes and im using.
All samplecode shown here attached.
SampleProject.zip

public class PlayerDbEntry
{
    public string? Key { get; set; }
    public string? Name { get; set; }
    public ClubDb? Club { get; set; }
}

public class ClubDb
{
    public string? Name { get; set; }
}

public class PlayerDToEntry
{
    public string? Key { get; set; }
    public string? PlayerName { get; set; }
    public ClubDto? ClubDto { get; set; }
}

public class ClubDto
{
    public string? ClubNameName { get; set; }
}

public class PlayerProfile : Profile
{
    public PlayerProfile()
    {
        CreateMap<PlayerDbEntry, PlayerDToEntry>()
            .ForMember(dest => dest.PlayerName, opt =>
            {
                opt.MapFrom(src => src.Name);
                opt.ExplicitExpansion();
            })
            .ForMember(dest => dest.ClubDto, opt =>
            {
                opt.MapFrom(src => src.Club);
                opt.ExplicitExpansion();
            })
            .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.Key));

        CreateMap<PlayerDToEntry, PlayerDbEntry>()
            .ForMember(dest => dest.Name, opt =>
            {
                opt.MapFrom(src => src.PlayerName);
                opt.ExplicitExpansion();
            })
            .ForMember(dest => dest.Club, opt =>
            {
                opt.MapFrom(src => src.ClubDto);
                opt.ExplicitExpansion();
            })
            .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.Key));


        CreateMap<ClubDb, ClubDto>()
            .ForMember(dest => dest.ClubNameName, opt => opt.MapFrom(src => src.Name))
            .ReverseMap();
    }
}

When i run the following code:

var config = new MapperConfiguration(cfg =>
{
    cfg.AddExpressionMapping();
    cfg.AddMaps(typeof(PlayerProfile));
});
var mapper = config.CreateMapper();
Expression<Func<PlayerDToEntry, PlayerDToEntry>> expression = (e) => new PlayerDToEntry
{
    PlayerName = e.PlayerName,
    ClubDto = new ClubDto
    {
        ClubNameName = e.ClubDto!.ClubNameName
    }
};

try
{
    var dbExpression = mapper.MapExpression<Expression<Func<PlayerDbEntry, PlayerDbEntry>>>(expression);
}
catch (Exception e)
{
    Console.WriteLine(e);
}

I encounter the exception below. the issue issue seems to be with the mapping between the SubObjects, any idear as to what could cause the issue?

System.InvalidOperationException: No coercion operator is defined between types 'SampleProject.ClubDto' and 'SampleProject.ClubDb'.
   at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type, MethodInfo method)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type)
   at AutoMapper.Extensions.ExpressionMapping.Extensions.VisitorExtensions.ConvertTypeIfNecessary(Expression expression, Type memberType)
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.DoBind(MemberInfo sourceMember, Expression initial, Expression mapped)
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.<>c__DisplayClass27_0.<GetMemberInit>b__0(List`1 list, MemberAssignmentInfo next)
   at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.GetMemberInit(MemberBindingGroup memberBindingGroup)
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.<MapExpression>g__MapBody|8_1[TDestDelegate](Dictionary`2 typeMappings, XpressionMapperVisitor visitor, <>c__DisplayClass8_0`1& )
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.<MapExpression>g__CreateVisitor|8_0[TDestDelegate](Dictionary`2 typeMappings, <>c__DisplayClass8_0`1& )
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IConfigurationProvider configurationProvider, LambdaExpression expression, Type typeSourceFunc, Type typeDestFunc, Func`3 getVisitor, Func`2 shouldConvertMappedBodyToDestType)
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression, Func`3 getVisitor, Func`2 shouldConvertMappedBodyToDestType)
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression)
   at Program.<Main>$(String[] args)

Yep - there was a bug - it was failing for custom expressions. The MyGet build should work now. Thanks for reporting.

Thanks that solved my issue :)
Any idea about when this will be part of an official release?