MapsterMapper / Mapster

A fast, fun and stimulating object to object Mapper
MIT License
4.32k stars 329 forks source link

Bug report: Exception thrown when use generic type paramaters. #614

Open codelovercc opened 1 year ago

codelovercc commented 1 year ago
public interface IDataLocalizer
{
    public CultureInfo CultureInfo { get; set; }
}

public interface ILocalizerDto : IMyDto
{
    public string CultureName { get; set; }

    public string CultureDisplayName { get; set; }

    public CultureInfo CultureInfo { get; }
}

public abstract class DataLocalizerRegister<TLocalizerDto, TDataLocalizer>
    where TLocalizerDto : ILocalizerDto where TDataLocalizer : IDataLocalizer
{
    public override void Register(TypeAdapterConfig config)
    {
        var parameter = Expression.Parameter(typeof(TDataLocalizer), "localizer");
        Expression memberAccessExp = Expression.Property(parameter, nameof(IDataLocalizer.CultureInfo));
        var dtoToEntitySetter = config.NewConfig<TLocalizerDto, TDataLocalizer>()
            .Ignore(Expression.Lambda<Func<TDataLocalizer, object>>(memberAccessExp, parameter))
            .AfterMapping((dto, localizer) => localizer.CultureInfo = CultureInfo.GetCultureInfo(dto.CultureName));

        var entityToDtoSetter = config.NewConfig<TDataLocalizer, TLocalizerDto>()
            .Map(dto => dto.CultureName, m => m.CultureInfo.Name)
            .Map(dto => dto.CultureDisplayName, m => m.CultureInfo.DisplayName);

        RegisterCustomMember(dtoToEntitySetter, entityToDtoSetter);
    }

    protected abstract override void RegisterCustomMember(
        TypeAdapterSetter<TLocalizerDto, TDataLocalizer> dtoToEntitySetter,
        TypeAdapterSetter<TDataLocalizer, TLocalizerDto> entityToDtoSetter);
}

After debug, memberAccessExp is worked and its debug view is "$localizer.CultureInfo" , so simple :) 'm => m.CultureInfo.Name` is not worked and its debug view is

.Lambda #Lambda1<System.Func`2[My.Data.Entities.MessageTemplateLocalizer,System.String]>(My.Data.Entities.MessageTemplateLocalizer $m)
{
    (((My.Models.Abstraction.IDataLocalizer)$m).CultureInfo).Name
}

and all the lambda in the method Register will convert toExpression like m => m.CultureInfo.Name, contains (My.Models.Abstraction.IDataLocalizer) case.

Stack trace:

System.ArgumentException: Allow only member access (eg. obj => obj.Child.Name) (Parameter 'lambda')
   at Mapster.Utils.ExpressionEx.GetMemberPath(LambdaExpression lambda, Boolean firstLevelOnly, Boolean noError)
   at Mapster.TypeAdapterSetter`2.Map[TDestinationMember,TSourceMember](Expression`1 member, Expression`1 source, Expression`1 shouldMap)
   at My.Services.Mapping.Mapster.Registers.DataLocalizerRegister`2.Register(TypeAdapterConfig config) in /somewhere/DataLocalizerRegister.cs:line 17
   at Mapster.TypeAdapterConfig.Apply(IEnumerable`1 registers)
   at Mapster.TypeAdapterConfig.Scan(Assembly[] assemblies)
...

If I change the class DataLocalizerRegister like below, it's working without any error.

public abstract class DataLocalizerRegister<TLocalizerDto, TDataLocalizer>
    where TLocalizerDto : AbstractLocalizerDto where TDataLocalizer : AbstractDataLocalizer

AbstractLocalizerDto and AbstractDataLocalizer are classes, not like the ILocalizerDto and IDataLocalizer they are interfaces.

I think this is a bug, m => m.CultureInfo.Name lambda is simple, method Mapster.Utils.ExpressionEx.GetMemberPath should support in this case.

.Net 7.0 C# 11.0 Mapster 7.3.0