Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map IEnumerable to List using AutoMapper

Tags:

c#

automapper

I am trying to map IEnumerable to List. I am not sure how to get it work. Here is what I have tried so far.

I am getting an error "Custom configuration for members is only supported for top-level individual members on a type."

Source: IEnumerable<Source>
Target: List<Target>

AutoMapper.Mapper.Map(sourceIEnum, TargetList);

Mapper.CreateMap<IEnumerable<Source>, List<Target>>()
    .ForMember(f => f, mp => mp.MapFrom(
                                    mfrom => mfrom.Select(s => AutoMapper.Mapper.Map(s, new Target())
                                ).ToList())
              );

Mapper.CreateMap<Source, Target>()
    .ForMember(f => f.TargetPropertyA, mp => mp.MapFrom(mfrom => mfrom.FromA.Value))
    .ForMember(f => f.TargetPropertyB, mp => mp.MapFrom(mfrom => mfrom.FromB.Value))
    .ForMember(f => f.TargetPropertyC, mp => mp.MapFrom(mfrom => mfrom.FromC.Value))
    .ForMember(f => f.InnerObjectTarget, mp => mp.MapFrom(
                                    mfrom => mfrom.Select(s => AutoMapper.Mapper.Map(s, new InnerObjectTarget())
                                ).ToList())
              );    

Mapper.CreateMap<SourceInner, TargetInner>()
        .ForMember(f => f.TargetInnerPropA, m => m.MapFrom(source => source.InnerA))
        .ForMember(f => f.TargetInnerPropB, m => m.MapFrom(source => source.InnerB))
        .ForMember(f => f.TargetInnerPropC, m => m.MapFrom(source => source.InnerC));
like image 648
CoolArchTek Avatar asked Apr 05 '14 03:04

CoolArchTek


2 Answers

There a several ways to map an IEnumerable<Source> to an IEnumerable<Target>. I'll show the three most convenient ones.

Taking the AdventureWorks database (2008R2) as an example, I defined three DTO classes:

class ProductModelDto
{
    public string Name { get; set; }
    public IEnumerable<ProductDto> Products { get; set; }
}

class ProductDto
{
    public string Name { get; set; }
    public string Number { get; set; }
    public IEnumerable<ProductReviewDto> ProductReviews { get; set; }
}

class ProductReviewDto
{
    public string ReviewerName { get; set; }
    public string Email { get; set; }
}

Here's the only mapping I defined:

Mapper.CreateMap<ProductModel, ProductModelDto>();
Mapper.CreateMap<Product, ProductDto>()
      .ForMember(dto => dto.Number, m => m.MapFrom(p => p.ProductNumber));
Mapper.CreateMap<ProductReview, ProductReviewDto>()
      .ForMember(dto => dto.Email, m => m.MapFrom(pr => pr.EmailAddress));

Just a query:

// A ProductModel having a Product with ProductReviews
var query = db.ProductModels.Where(pm => pm.ProductModelID == 64);

Now the most convenient ways, in my opinion, to map the source ProductModels to IEnumerable<ProductModelDto> are these three:

1. query.Select(Mapper.Map<ProductModelDto>)
2. Mapper.Map<List<ProductModelDto>>(query)
3. query.ProjectTo<ProductModelDto>() (Project().To<> prior to v. 4.1.0)

And if you do ToList(), you've mapped IEnumerable to List.

The output is the same for all three alternatives:

- HL Mountain Pedal
  - HL Mountain Pedal; PD-M562
    - David; [email protected]
    - Jill; [email protected]

As you see, AutoMapper doesn't need explicit mapping definitions for the IEnumerables. Not for the top level and not for the nested collections. It also maps a nested collection when the names are identical in the source and target classes and a mapping is defined for the elements in the collection.

Case 3 is a special case. It projects the IQueryable to the target, which is still an IQueryable. Predicates applied after the mapping are still translated to SQL and the query tries to select only the fields from the database that are necessary for projection. The latter doesn't always succeed when DTOs are nested though, as in this case. Linq-to SQL appeared to execute two queries with option 3 (but three for 1 and 2).

like image 102
Gert Arnold Avatar answered Sep 28 '22 01:09

Gert Arnold


As long as Source to Target mapping is defined, you don't need to define IEnumerable<Source> to List<Target> mapping explicitly.

Automapper is smart enough to map the basic collections automatically (List, IEnumerable, Collection etc.) if the collection element types are mapped. so remove all code between collection mapping and try again.

This snippet..

Mapper.CreateMap<IEnumerable<Source>, List<Target>>()
    .ForMember(f => f, mp => mp.MapFrom(
                                    mfrom => mfrom.Select(s => AutoMapper.Mapper.Map(s, new Target())
                                ).ToList())
              );
like image 28
Raja Nadar Avatar answered Sep 28 '22 02:09

Raja Nadar