Szymon Kaszuba
Szymon Kaszuba

Reputation: 23

How to CreateMap for Entity and DTO with ICollection and IList

I have a problem with mapping in AutoMapper.
I try use ForMember function, but still get a error connecting with mapping.
How to CreateMap for this Entities?

I created a AutoMapping class:

    public class AutoMapping : Profile
    {
        public AutoMapping()
        {
            CreateMap<Member, MemberDto>()
                .ForMember(dest => dest.Groups, opt => { opt.MapFrom(src => src.GroupMember.Select(x => x.Group)); });
            CreateMap<Group, GroupDto>()
                .ForMember(dest => dest.Members, opt => { opt.MapFrom(src => src.GroupMember.Select(x => x.Member)); });
        }
    }

And I tried use it like this:

    public async Task<Page<MemberDto>> GetPaginatedAsync(IDataTablesRequest request)
    {
        Filters<MemberDto> filters = GetFiltersFromRequest(request);
        Sorts<MemberDto> sorts = GetSortsFromRequest(request);
        var currentPage = request.Start / request.Length + 1;
        var pageSize = request.Length;

        try
        {
            return await _context.Member
                .Include(gm => gm.GroupMember)
                .ThenInclude(g => g.Group)
                .Select(m => _mapper.Map<MemberDto>(m))
                .PaginateAsync(currentPage, pageSize, sorts, filters);
        }
        catch (Exception ex)
        {
            throw ex; //Log it
        }
    }

In next steps I will make the same with rest of my Entities, but please give me some tips :)

EDIT: After use

                    .ProjectTo<MemberDto>(_mapper.ConfigurationProvider)
                    .PaginateAsync(currentPage, pageSize, sorts, filters);

I get error like

The LINQ expression 'DbSet<Member>
    .Where(m => DbSet<GroupMember>
        .Where(g => EF.Property<Nullable<int>>(m, "MemberId") != null && EF.Property<Nullable<int>>(m, "MemberId") == EF.Property<Nullable<int>>(g, "MemberId"))
        .Join(
            outer: DbSet<Group>, 
            inner: g => EF.Property<Nullable<int>>(g, "GroupId"), 
            outerKeySelector: g0 => EF.Property<Nullable<int>>(g0, "GroupId"), 
            innerKeySelector: (o, i) => new TransparentIdentifier<GroupMember, Group>(
                Outer = o, 
                Inner = i
            ))
        .Select(g => new GroupDto{ 
            CreatedDate = g.Inner.CreatedDate, 
            DeletedDate = g.Inner.DeletedDate, 
            GroupId = g.Inner.GroupId, 
            IsActive = g.Inner.IsActive, 
            Members = DbSet<GroupMember>
                .Where(g1 => EF.Property<Nullable<int>>(g.Inner, "GroupId") != null && EF.Property<Nullable<int>>(g.Inner, "GroupId") == EF.Property<Nullable<int>>(g1, "GroupId"))
                .Join(
                    outer: DbSet<Member>, 
                    inner: g1 => EF.Property<Nullable<int>>(g1, "MemberId"), 
                    outerKeySelector: m0 => EF.Property<Nullable<int>>(m0, "MemberId"), 
                    innerKeySelector: (o, i) => new TransparentIdentifier<GroupMember, Member>(
                        Outer = o, 
                        Inner = i
                    ))
                .Select(g1 => new MemberDto{ 
                    CreatedDate = g1.Inner.CreatedDate, 
                    DateOfBirth = g1.Inner.DateOfBirth, 
                    Email = g1.Inner.Email, 
                    FirstName = g1.Inner.FirstName, 
                    IsActive = g1.Inner.IsActive, 
                    LastName = g1.Inner.LastName, 
                    MemberId = g1.Inner.MemberId, 
                    PhoneNumber = g1.Inner.PhoneNumber, 
                    UpdatedDate = g1.Inner.UpdatedDate 
                }
                )
                .ToList(), 
            Name = g.Inner.Name, 
            UpdatedDate = g.Inner.UpdatedDate 
        }
        )
        .ToList()
        .Any(g => g.Name.Contains(__column_Search_Value_0)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

EDIT2: I use it for Paginate and Filtering:
https://github.com/wdunn001/EntityFrameworkPaginateCore/tree/master/EntityFrameworkPaginateCore

EDIT3:

private static Sorts<MemberDto> GetSortsFromRequest(IDataTablesRequest request)
{
    var sorts = new Sorts<MemberDto>();
    foreach (var column in request.Columns.Where(x => x.IsSortable && x.Sort != null))
    {
        switch (column.Field)
        {
            case "firstName":
                sorts.Add(true, x => x.FirstName, column.Sort.Direction != SortDirection.Ascending);
                break;
            case "lastName":
                sorts.Add(true, x => x.LastName, column.Sort.Direction != SortDirection.Ascending);
                break;
            case "email":
                sorts.Add(true, x => x.Email, column.Sort.Direction != SortDirection.Ascending);
                break;
            case "phoneNumber":
                sorts.Add(true, x => x.PhoneNumber, column.Sort.Direction != SortDirection.Ascending);
                break;
            default:
                break;
        }
    }

    return sorts;
}

private static Filters<MemberDto> GetFiltersFromRequest(IDataTablesRequest request)
{
    var filters = new Filters<MemberDto>();
    foreach (var column in request.Columns
        .Where(x => x.IsSearchable && x.Search != null && 
                    !string.IsNullOrWhiteSpace(x.Search.Value))
        )
    {
        switch (column.Field)
        {
            case "firstName":
                filters.Add(true, x => x.FirstName.Contains(column.Search.Value));
                break;
            case "lastName":
                filters.Add(true, x => x.LastName.Contains(column.Search.Value));
                break;
            case "email":
                filters.Add(true, x => x.Email.Contains(column.Search.Value));
                break;
            case "phoneNumber":
                filters.Add(true, x => x.PhoneNumber.Contains(column.Search.Value));
                break;
            case "groups":
                filters.Add(true, x => x.Groups.Any(g => g.Name.Contains(column.Search.Value)));
                break;
            default:
                break;
        }
    }

    return filters;
}

EDIT4:

CreateMap<Member, MemberDto>()
                .ForMember(dest => dest.Groups, 
                           opt => { opt.MapFrom(src => src.GroupMember); });

After removing SELECT I get a error:

Missing map from CMSport.Context.Models.GroupMember to CMSport.Dto.GroupDto. Create using CreateMap<GroupMember, GroupDto>.



SOLUTION:
1. Make filter and sorts by Member, not by MemberDto
2. Set Dtos as [JsonObject(IsReference = true)]
3. Map it after PaginateAsync like this

var page = await _context.Member
    .Include(x => x.GroupMember)
    .ThenInclude(x => x.Group)
    .PaginateAsync(currentPage, pageSize, sorts, filters);

return new Page<MemberDto>
{
    CurrentPage = page.CurrentPage,
    PageCount = page.PageCount,
    PageSize = page.PageSize,
    RecordCount = page.RecordCount,
    Results = page.Results.Select(x => _mapper.Map<MemberDto>(x))
};

Upvotes: 0

Views: 903

Answers (1)

E. Shcherbo
E. Shcherbo

Reputation: 1148

I suppose that you have an error related to translating LINQ to SQL, in the

.Select(m => _mapper.Map<MemberDto>(m))

Obviously, _mapper.Map<MemberDto>(m) can't be translated to SQL, therefore instead of that line you can try to use .ProjectTo<MemberDto>() (see QuarableExtensions) so that the query will look like

return await _contesnxt.Member
            .ProjectTo<MemberDto>()     
            .PaginateAsync(currentPage, pageSize, sorts, filters);

Upvotes: 1

Related Questions