3
votes

I have two Model classes Author and Book and two DTO classes BookDTO and BookDetailDTO as shown below

public class Author
{
    public int Id { get; set; }
    [Required()]
    public string Name { get; set; }
}

public class Book 
{
    public int Id { get; set; }
    [Required()]
    public string Title { get; set; }
    public int Year { get; set; }
    public decimal Price { get; set; }
    public string Genre { get; set; }
    public int AuthorId { get; set; }
    public virtual Author Author { get; set; }           
}

public class BookDTO
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string AuthorName { get; set; }
}

public class BookDetailDTO
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Year { get; set; }
    public decimal Price { get; set; }
    public string AuthorName { get; set; }
    public string Genre { get; set; }
}

I use AutoMapper to do the mapping as shown below

public class MapConfig
{
    public static void RegisterMapping()
    {
        AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Book, BookDTO>()
            .ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name)));
        AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Book, BookDTO>()
            .ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name)).ReverseMap());


        //AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Book, BookDetailDTO>()
            //.ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name)));
        //AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Book, BookDetailDTO>()
            //.ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name)).ReverseMap());

    }
}

Now I get the list of books through BooksController

public class BooksController : ApiController
{
    private BookServiceContext db = new BookServiceContext();


    public IQueryable<BookDTO> GetBooks()
    {

        var books = AutoMapper.Mapper.Map<IEnumerable<Book>, IEnumerable<BookDTO>>(db.Books);
        return books.AsQueryable();
    }
}

It list the list of books.

But when I un-comment the mappings for BookDetailDTO in MapConfig.cs, and I try to get the list of Books, It throws the following error

Mapping types: IEnumerable1 -> IEnumerable1 System.Collections.Generic.IEnumerable1[[BookService.Models.Book, BookService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> System.Collections.Generic.IEnumerable1[[BookService.Models.BookDTO, BookService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]

I need the mapping from Book to BookDetailDTO to display the details of the book. What am I doing wrong? I use AutoMapper 5.1.1

3

3 Answers

0
votes

In the MapConfig class, you defined the map twice, Book -> BookDTO, then Book -> BookDetailDTO. The second map definition overridden the first definition, automapper will complaint when you call Mapper.Map, IEnumerable>(db.Books), because automapper did not know how to map Book -> BookDTO. That's was the reason you got that error message.

To solve the problem, you either define Book -> BookDTO or Book -> BookDetailDTO, not both.

0
votes

This one fixed my issue, I think we have to put all mappings inside the initialize method

      AutoMapper.Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<Book, BookDTO>()
                .ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name));
            cfg.CreateMap<Book, BookDetailDTO>()
             .ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name));
        });

Even though this fixed my problem, I am not sure this is the best way to do it. If any body has better answer to how to map all mappings in a project, please share the same.

0
votes

1-) You over configured it. I removed the unnecessary parts:

public class MapConfig
{
    public static void RegisterMapping()
    {
        AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Book, BookDTO>()
            .ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name)).ReverseMap());


        AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Book, BookDetailDTO>()
            .ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name)).ReverseMap());

    }
}

2-) Dont use Mapper.Map method because Mapper.Map executes the query. Instead, use ProjectTo. It doesn't execute the query. It edits your ef query. It is better for performance

Install this enter image description here

ProjectTo support returning IQuerable objects.

public IQueryable<BookDTO> GetBooks()
{
    return db.Books.ProjectTo<BookDTO>();
}

For More information

https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions