AutoMapper / AutoMapper.Extensions.ExpressionMapping

MIT License
143 stars 39 forks source link

Support generic type argument inference when mapping method calls #99

Closed mycroes closed 3 years ago

mycroes commented 4 years ago

This is mostly a proof of concept. It works, passes all unit tests and passes a few new unit tests. It does solve #97, although it doesn't change anything about the exception that's thrown from the example using Mapper.MapExpression() that's used there.

I guess that in some sense it's an improvement over GetConvertingTypeIfExists since it infers the types from the generic type parameters instead of trying to match it to a single target type.

Let me know what you think.

BlaiseD commented 4 years ago

We can do this for UseAsDataSource() given that was the initial intention.

As you've noted MapExpression() and UseAsDataSource() have different mapping implementations.

I disagree because of the potential for more work e.g. the following works with CreateMapper1() but not with CreateMapper() (after changing Model.Value to decimal) - also fails with the existing implementation. Sometimes less work is more maintainable.

        public void When_Apply_Where_Clause_Over_Queryable_As_Data_Source()
        {
            // Arrange
            var mapper = CreateMapper();
            var models = new List<Model> {new Model {Value = 1}, new Model {Value = 2 } };
            var queryable = models.AsQueryable();

            Expression<Func<DTO, bool>> dtoPropertyFilter = (dto) => dto.Value == 1;

            // Act
            var result = queryable.UseAsDataSource(mapper).For<DTO>().Where(dtoPropertyFilter).ToList();

            // Assert
            result.ShouldNotBeNull();
            result.Count.ShouldBe(1);
        }

        private static IMapper CreateMapper()
        {
            var mapperConfig = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Model, DTO>();
            });

            var mapper = mapperConfig.CreateMapper();
            return mapper;
        }

        private static IMapper CreateMapper1()
        {
            var mapperConfig = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Model, DTO>()
                    .ForMember(src => src.Value, opts => opts.MapFrom(dest => (int?)dest.Value));
            });

            var mapper = mapperConfig.CreateMapper();
            return mapper;
        }

        private class Model
        {
            public DateTimeOffset Timestamp { get; set; }
            public decimal Value { get; set; }
        }

        private class DTO
        {
            public DateTimeOffset? Timestamp { get; set; }
            public int? Value { get; set; }
        }

We want to keep MapExpression working as is.