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()?
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
I wonder if sorting the Bars inside the Queryable during mapping is the best place to do this? Wouldn't placing the sort in the map mean that it sorts at inappropriate times?
For example, what if you just wanted to know if the Bars collection contained 1 item? Calling FooDto.Bars.Any()
would still result in a sort in the database.
Perhaps it would be better to have another property in the FooDTO (ignored in the mapping) that looks like this:
public IEnumerable<BarDto> SortedBars
{
get
{
if (Bars == null)
return null;
return Bars.OrderBy(x => x.SortOrder).ToList();
}
}
链接地址: http://www.djcxy.com/p/37412.html