Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper Project().To() and sorting a child collection

I have an object graph that I'm loading from a database using EF CodeFirst and AutoMapper into DTOs:-

public class Foo
{
  public int Id { get; set; }
  public virtual ICollection<Bar> Bars { get; set; }
}

public class Bar
{
  public int Id { get; set; }
  public int FooId { get; set; }
  public virtual Foo Foo { get; set; }

  public string Name { get; set; }
  public int SortOrder { get; set; }
}

public class FooDto
{
  public IEnumerable<BarDto> Bars { get; set; }
}

public class BarDto
{
  public string Name { get; set; }
  public int SortOrder { get; set; }
}

My mappings look like:-

mapper.CreateMap<Foo, FooDto>();
mapper.CreateMap<Bar, BarDto>();

So far, so good. I can grab the entities from my context and project to the DTO nicely:-

var foos = context.Foos.Project().To<FooDto>();

What I can't do with this approach, however, is sort the Bars by their SortOrder inside the IQueryable.

If I try:-

mapper.CreateMap<Foo, FooDto>()
  .ForMember(
    x => x.Bars
    opt => opt.MapFrom(src => src.Bars.OrderBy(x => x.SortOrder)));
mapper.CreateMap<Bar, BarDto>();
var foos = context.Foos.Project().To<FooDto>();

I get an exception:-

System.InvalidOperationException: Sequence contains no elements
  at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
  at AutoMapper.MappingEngine.CreateMapExpression(Type typeIn, Type typeOut)
  ...

Seems this is related to https://github.com/AutoMapper/AutoMapper/issues/159 - though I'm already using a complex type for the child collection. I guess CreateMapExpression doesn't support OrderBy on child collections?

If I'm not using .Project().To() then I can sort the child collection easily:-

var model = context.Foos.Select(x => new FooDto()
{
  Bars = x.Bars.OrderBy(y => y.SortOrder)
});

but then I have to repeat the mapping wherever I want to use it, defeating the purpose of using AutoMapper.

Curiously:-

1) I can perform other (more complicated?) operations on the child collection and flatten those into my parent DTO no problem:-

mapper.CreateMap<Foo, FooDto>()
  .ForMember(
    x => x.AllBarsHaveAName,
    opt => opt.MapFrom(src =>
      src.Bars.All(x => x.Name != null)));

2) I can Mapper.Map<FooDto>(foo); in memory just fine, and it'll sort the bars no problem.

It's possible to sort the child collection at the IQueryable level while still using .Project().To()?

like image 571
Iain Galloway Avatar asked Feb 14 '13 12:02

Iain Galloway


People also ask

When should you not use AutoMapper?

If you have to do complex mapping behavior, it might be better to avoid using AutoMapper for that scenario. Reverse mapping can get very complicated very quickly, and unless it's very simple, you can have business logic showing up in mapping configuration.

Can AutoMapper map collections?

Polymorphic element types in collectionsAutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.

What is the use of AutoMapper in C#?

AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.

What is the use of AutoMapper in MVC?

AutoMapper is an object-object mapper that allows you to solve the problem of manually mapping each property of a class with the same properties of another class. Before AutoMapper was introduced if we wanted to assign one object property to another object property then we were following a long procedure.


1 Answers

Ended up modifying the AutoMapper source code to support this scenario. Hopefully the proposed fix will be accepted, but in the meantime you can see the details at:-

https://github.com/AutoMapper/AutoMapper/pull/327

like image 138
Iain Galloway Avatar answered Oct 18 '22 18:10

Iain Galloway