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?
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With