Automapping using open generics and including the source in a ForMember statement
Asked Answered
A

1

7

I've recently upgraded from Automapper 4.2.1 to 5.1.1 and am having issues with a previously valid mapping involving open generics.

Previously, within the automapper configuration, I had the following open generic mapping configuration

CreateMap(typeof(IPager<>), typeof(ModelPager<>))
    .ForMember("Items", e => e.MapFrom(o => (IEnumerable) o));

This works in Automapper 4 but fails in 5 with a InvalidOperaionException when attempting to map via IMapper.Map<TDestination>(source). It appears to fail when executing the mapping of the Items ForMember operation with an exception message of "Sequence contains no matching element"

As reflected in the example implementation code below IPager<TSource> implements IEnumerable<TSource>, and the Items property of ModelPager<TDestination> is an IEnumerable<TDestination> so the cast should be valid. and there exist a valid mapping for each TSource to TDestination

CreateMap<TSource, TDestination>();

IPager interface

public interface IPager<out TItem> : IEnumerable<TItem>
{
    int CurrentPage { get; }

    int PageCount { get; }

    int PageSize { get; }

    int TotalItems { get; }
}

IPager implementation

public class Pager<TItem> : IPager<TItem>
{
    private readonly IEnumerable<TItem> _items;

    public Pager(IEnumerable<TItem> items,
                 int currentPage,
                 int pageSize,
                 int totalItems)
    {
        /// ... logic ... 
        this._items = items ?? Enumerable.Empty<TItem>();
        this.CurrentPage = currentPage;
        this.PageSize = pageSize;
        this.TotalItems = totalItems;
    }

    public int CurrentPage { get; }

    public int PageCount => (this.TotalItems + this.PageSize - 1) / this.PageSize;

    public int PageSize { get; }

    public int TotalItems { get; }

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

    public IEnumerator<TItem> GetEnumerator() => this._items.GetEnumerator();
}

ModelPager

public class ModelPager<TItem>
{
    public int CurrentPage { get; set; }

    public IEnumerable<TItem> Items { get; set; }

    public int PageCount { get; set; }

    public int PageSize { get; set; }

    public int TotalItems { get; set; }
}

What is the proper way to map this in Automapper 5 without either abandoning open generics by explicitly mapping each possible mapping, or by using a custom open generic type converter that would require me to manually map all properties and use reflection to resolve the open types for assignment?

Awe answered 16/8, 2016 at 19:49 Comment(3)
This looks like a bug, can you open a GitHub issue?Tandem
@JimmyBogard can doAwe
Issue #1624 has been submittedAwe
A
8

Given this looks to be a bug (AutoMapper #1624), a work around can be done with a custom open generic TypeConverter that does not require reflection.

The mapping should be changed to something along the lines of

CreateMap(typeof(IPager<>), typeof(ModelPager<>))
    .ConvertUsing(typeof(PagerToModelPagerConverter<,>));

with a custom ITypeConverter

public class PagerToModelPagerConverter<TSource, TDestination> : ITypeConverter<IPager<TSource>, ModelPager<TDestination>>
{
    public ModelPager<TDestination> Convert(IPager<TSource> source,
                                            ModelPager<TDestination> destination,
                                            ResolutionContext context)
    {
        var list = source.ToList(); // avoid redundant iterations
        var itemMapping = context.Mapper.Map<IEnumerable<TSource>, IEnumerable<TDestination>>(list);

        var modelPager = new ModelPager<TDestination>
                         {
                             CurrentPage = source.CurrentPage,
                             Items = itemMapping,
                             PageCount = source.PageCount,
                             PageSize = source.PageSize,
                             TotalItems = source.TotalItems
                         };

        return modelPager;
    }
Awe answered 16/8, 2016 at 21:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.