AutoMapper / AutoMapper.Extensions.ExpressionMapping

MIT License
143 stars 39 forks source link

Error: Dto Type With 'in' Parameter #165

Closed biqas closed 1 year ago

biqas commented 1 year ago

Hello,

if a dto has a method with 'in' parameter it is throwing an Exception. For 'ref' and 'out' would understand why the limitation is there but for 'in' I can't see any plausible reason?

BlaiseD commented 1 year ago

Best to provide a failing sample.

biqas commented 1 year ago
public record TenantDto
{
    public Guid UID { get; init; }
    public string Name { get; set; }
    public MetaDataDto MetaData { get; init; }
    public UserDto[] Users { get; init; } = Array.Empty<UserDto>();

    public static explicit operator TenantDto(in TenantEntity entity)
    {
        return new TenantDto
        {
            UID      = entity.UID,
            Name     = entity.CanonicalName,
            MetaData = (MetaDataDto)entity.MetaData,
            Users    = entity.Users.Select(x => (UserDto)x).ToArray()
        };
    }
}

public record MetaDataDto
{
    public DateTimeOffset CreatedOn { get; init; }
    public string CreatedBy { get; init; }

    public static explicit operator MetaDataDto(MetaDataEntity entity)
    {
        return new MetaDataDto
        {
            CreatedOn = entity.CreatedOn,
            CreatedBy = entity.CreatedBy
        };
    }
}

public record UserDto
{
    public Guid UID { get; init; }
    public Guid TenantUID { get; init; }
    public TenantDto? Tenant { get; init; }
    public MetaDataDto MetaData { get; init; }

    public static explicit operator UserDto(UserEntity entity)
    {
        return new UserDto
        {
            UID       = entity.UID,
            TenantUID = entity.TenantUID,
            Tenant    = entity.Tenant is null ? default : (TenantDto)entity.Tenant,
            MetaData  = (MetaDataDto)entity.MetaData,
        };
    }
}

public record TenantEntity
{
    public Guid UID { get; init; }
    public MetaDataEntity MetaData { get; init; }
    public HashSet<UserEntity> Users { get; init; } = new();
}

public record MetaDataEntity
{
    public DateTimeOffset CreatedOn { get; init; }
    public string CreatedBy { get; init; }
}

public record UserEntity
{
    public Guid UID { get; init; }
    public Guid TenantUID { get; init; }
    public TenantEntity? Tenant { get; init; }
    public MetaDataEntity MetaData { get; init; }
}

var mapper = new MapperConfiguration(cfg =>
{
    cfg.AddExpressionMapping();

    cfg.CreateMap<TenantDto, TenantEntity>();

    cfg.CreateMap<TenantEntity, TenantDto>()
        .ForMember(dest => dest.Users, opt => opt.MapFrom(src => src.Users.ToArray()));
})
.CreateMapper();

Expression<Func<TenantDto, bool>> dtoExpression = x => x.Users.Length > 2;
mapper.MapExpression<Expression<Func<TenantEntity, bool>>>(dtoExpression);

This line: makes the problem: public static explicit operator TenantDto(in TenantEntity entity)

BlaiseD commented 1 year ago

What you posted worked - after including a missing property:

    public class AnotherTest
    {
        [Fact]
        public void Testmethod()
        {
            var mapper = new MapperConfiguration(cfg =>
            {
                cfg.AddExpressionMapping();

                cfg.CreateMap<TenantDto, TenantEntity>();

                cfg.CreateMap<TenantEntity, TenantDto>()
                    .ForMember(dest => dest.Users, opt => opt.MapFrom(src => src.Users.ToArray()));
            }).CreateMapper();

            Expression<Func<TenantDto, bool>> dtoExpression = x => x.Users.Length > 2;
            var mapped = mapper.MapExpression<Expression<Func<TenantEntity, bool>>>(dtoExpression);
        }
        public record TenantDto
        {
            public Guid UID { get; init; }
            public string Name { get; set; }
            public MetaDataDto MetaData { get; init; }
            public UserDto[] Users { get; init; } = Array.Empty<UserDto>();

            public static explicit operator TenantDto(in TenantEntity entity)
            {
                return new TenantDto
                {
                    UID = entity.UID,
                    Name = entity.CanonicalName,
                    MetaData = (MetaDataDto)entity.MetaData,
                    Users = entity.Users.Select(x => (UserDto)x).ToArray()
                };
            }
        }

        public record MetaDataDto
        {
            public DateTimeOffset CreatedOn { get; init; }
            public string CreatedBy { get; init; }

            public static explicit operator MetaDataDto(MetaDataEntity entity)
            {
                return new MetaDataDto
                {
                    CreatedOn = entity.CreatedOn,
                    CreatedBy = entity.CreatedBy
                };
            }
        }

        public record UserDto
        {
            public Guid UID { get; init; }
            public Guid TenantUID { get; init; }
            public TenantDto? Tenant { get; init; }
            public MetaDataDto MetaData { get; init; }

            public static explicit operator UserDto(UserEntity entity)
            {
                return new UserDto
                {
                    UID = entity.UID,
                    TenantUID = entity.TenantUID,
                    Tenant = entity.Tenant is null ? default : (TenantDto)entity.Tenant,
                    MetaData = (MetaDataDto)entity.MetaData,
                };
            }
        }

        public record TenantEntity
        {
            public string CanonicalName { get; set; }//added this
            public Guid UID { get; init; }
            public MetaDataEntity MetaData { get; init; }
            public HashSet<UserEntity> Users { get; init; } = new();
        }

        public record MetaDataEntity
        {
            public DateTimeOffset CreatedOn { get; init; }
            public string CreatedBy { get; init; }
        }

        public record UserEntity
        {
            public Guid UID { get; init; }
            public Guid TenantUID { get; init; }
            public TenantEntity? Tenant { get; init; }
            public MetaDataEntity MetaData { get; init; }
        }
    }

Maybe your error is unrelated?

biqas commented 1 year ago

Thanks for checking this, somehow, I'm also not able to reproduce this. It had an Exception with "Type has ref/out param". If I can reproduce once, I will reopen or create another issue.