Entity core Mapping entity types with join table

2019-06-07 05:17发布

问题:

I've started using entity core and I ran into one problem. I'm using automapper in my project and don't know how to populate my join table(BookAuthor).

In MVC I worked with ICollections but entity core does not yet support working with many to many relationship entities and now I have to work with join table.

Question: How can i map my Bookviewmodel to the Book? I don't know what should I do with my BookAuthors list. Maybe I shouldn't populate it?

I tried something like that but it's not working.

CreateMap<BookViewModel, Book>()
.ForMember(b => b.BookAuthors, opt => opt.MapFrom(b => b.Authors.Select(a => new BookAuthor
            {
                AuthorId = a.AuthorId
            }

Models(some properties removed for brevity)

 public class Book
{
    public int BookId { get; set; }

    public List<BookAuthor> BookAuthors { get; set; }
}
public class Author
    {
        public int AuthorId { get; set; }
        public string AuthorName { get; set; }
        public List<BookAuthor> BookAuthors { get; set; }
    }
 public class BookAuthor
{
    public int BookId { get; set; }
    public Book Book { get; set; }

    public int AuthorId { get; set; }
    public Author Author { get; set; }
}
public class BookViewModel
{
    public int BookId { get; set; }

    public virtual List<AuthorViewModel> Authors { get; set; }    
}
public class AuthorViewModel
    {
        public int AuthorId { get; set; }

        public string AuthorName { get; set; }
    }
public class Library : DbContext
{
    public DbSet<Book> Books { get; set; }

    public DbSet<Author> Authors { get; set; }
}

The working version of the mapping

 .ForMember(dto => dto.Authors, opt => opt.MapFrom(b => b.BookAuthors.Select(a=>a.Author).ToList()));
        CreateMap<List<BookAuthor>, List<AuthorViewModel>>();
.PreserveReferences()//don't forget about this row or you will get an error
.ForMember(b => b.BookAuthors, opt => opt.MapFrom(b => b.Authors
    .Select(a => new { b.BookId, Book = b, a.AuthorId, Author = a })));

回答1:

Here is the desired mapping configuration:

CreateMap<Book, BookViewModel>()
    // Book -> BookViewModel
    .ForMember(b => b.Authors, opt => opt.MapFrom(b => b.BookAuthors
        .Select(ba => ba.Author)))
    .ReverseMap()
    // BookViewModel -> Book
    .PreserveReferences()
    .ForMember(b => b.BookAuthors, opt => opt.MapFrom(b => b.Authors
        .Select(a => new { b.BookId, Book = b, a.AuthorId, Author = a })))
    ;

CreateMap<Author, AuthorViewModel>()
    // Author -> AuthorViewModel
    .ReverseMap()
    // AuthorViewModel -> Author
    ;

The most important thing is to realize that AutoMapper does not require the type of the returned expression from MapFrom to match the type of the destination. If it doesn't match, then AutoMapper will try to map the returned type to the destination type using the corresponding mapping if any. This allows you to reuse the mappings.

In order to convert BookAuthor to AuthorViewModel, you first convert it to Author (by simply extracting the Author property) like if it was a collection of Author, and let AM convert the Author to AuthorViewModel using the corresponding mapping.

In order to convert AuthorViewModel from BookViewModel.Authors to BookAuthor, you convert it first to anonomymous type similar to BookAuthor, but with corresponding Book and Author property types being ViewModel types and let AM convert it to BookAuthor using corresponding mappings. PreserveReferences() is used to avoid stack overflow due to circular reference model.