.netentity-framework-coreautomappermapperprojectto

How to add custom Method to ProjectTo in AutoMapper?


I'm using .NET Core 7 and AutoMapper 12 and I'm trying to create a map from an Entity Framework query to a list of DTO's. As I need a list of DTO's, I'm using the .ProjectTo() function from AutoMapper

var configuration = new MapperConfiguration(cfg =>
{
    cfg.CreateProjection<MachineSchedule, MachineScheduleDataDto>()
        .ForMember(m => m.ShiftsDto, opts => opts.MapFrom(m => AddShiftCondition(m.Shifts, shiftType.Value))) // HERE
        .ForMember(m => m.ScheduledStopsDto, opts => opts.MapFrom(m => m.ScheduledStops))
        .ForMember(m => m.MachineOperationsDto, opts => opts.MapFrom(m => m.MachineOperations));

    cfg.CreateProjection<MachineOperation, MachineOperationDto>()
        .ForMember(m => m.EggQuantitiesDto, opts => opts.MapFrom(m => m.EggQuantities));

    cfg.CreateProjection<EggQuantity, EggQuantityDto>();

    cfg.CreateProjection<Shift, ShiftDto>();

    cfg.CreateProjection<ScheduledStop, ScheduledStopDto>();
});

var queryMapper = configuration.CreateMapper();

var diffDays = GetWeekdaysBetweenDates(startDate, endDate);

var test = queryMapper.ProjectTo<MachineScheduleDataDto>(_typedContext?
        .AsNoTracking()
        .Include(m => m.Shifts)
        .Include(m => m.ScheduledStops)
        .Include(m => m.MachineOperations)!
        .ThenInclude(m => m.EggQuantities)
    .OrderBy(m => m.MachineScheduleId));

As you can see, I have the method AddShiftCondition:

private static IEnumerable<Shift>? AddShiftCondition(ICollection<Shift>? shifts, EShiftType? shiftType)
{
    if (shifts != null && !shifts.Any())
        return null;

    if (shiftType.HasValue)
        return shifts!.Where(s => s.Type == shiftType.Value);

    return shifts;
}

I don't know why, but if I use .ProjectTo(), the AddShiftCondition is not called, but if I use .Map() instead, all works fine:

var configuration = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<MachineSchedule, MachineScheduleDataDto>()
        .ForMember(m => m.ShiftsDto, opts => opts.MapFrom(m => AddShiftCondition(m.Shifts, shiftType.Value)))
        .ForMember(m => m.ScheduledStopsDto, opts => opts.MapFrom(m => m.ScheduledStops))
        .ForMember(m => m.MachineOperationsDto, opts => opts.MapFrom(m => m.MachineOperations));

    cfg.CreateMap<MachineOperation, MachineOperationDto>()
        .ForMember(m => m.EggQuantitiesDto, opts => opts.MapFrom(m => m.EggQuantities));

    cfg.CreateMap<EggQuantity, EggQuantityDto>();

    cfg.CreateMap<Shift, ShiftDto>();

    cfg.CreateMap<ScheduledStop, ScheduledStopDto>();
});

var queryMapper = configuration.CreateMapper();

var diffDays = GetWeekdaysBetweenDates(startDate, endDate);

var test = queryMapper.Map<MachineScheduleDataDto>(_typedContext?
        .AsNoTracking()
        .Include(m => m.Shifts)
        .Include(m => m.ScheduledStops)
        .Include(m => m.MachineOperations)!
        .ThenInclude(m => m.EggQuantities)
    .FirstOrDefault());

Why does it happen?


Solution

  • The easiest way would be to just scrap the method and write the expression directly:

    cfg.CreateProjection<MachineSchedule, MachineScheduleDataDto>()
        .ForMember(m => m.ShiftsDto, 
            opts => opts.MapFrom(m => m.Shifts.Where(s => s.Type == shiftType.Value))) // add empty collection handling if needed, though I would keep it simple
    

    ProjectTo works with IQueryable and for ORM (like EF Core) it will result in translation of the LINQ query into actual SQL query and it is impossible to do (in general case) for some arbitrary method. So AutoMapper seems to ignore the call (though I would expect translation failure).

    Read more: