I have simple task: just map one class to another. For some fields I have complex logic, depends on 2 or more fields, so, I try to use ConvertUsing
(https://docs.automapper.org/en/stable/Custom-type-converters.html)
I use AutoMapper 10.0.0
My code is:
Source class:
public class DeviceStatusHistory
{
public DeviceStatusHistory()
{
DateChange = DateTime.UtcNow;
}
public int Id { get; set; }
public int DeviceId { get; set; }
public virtual Device Device { get; set; }
public int? RequestId { get; set; }
public virtual DeviceManagementRequest Request { get; set; }
public DeviceStatus OldStatus { get; set; }
public DeviceStatus NewStatus { get; set; }
public string Notes { get; set; }
public DateTime DateChange { get; set; }
}
DTO class:
public class DeviceChangeStatusDto
{
public int DeviceId { get; set; }
public string CarrierName { get; set; }
public string DeviceName { get; set; }
public string DeviceIMEI { get; set; }
public string OldStatus { get; set; }
public string NewStatus { get; set; }
public string Reason { get; set; }
public DateTime DateChange { get; set; }
}
and Automapper class:
public class AutoMapperEfDeviceManagement : AutoMapper.Profile
{
public AutoMapperEfDeviceManagement()
{
CreateMap<DeviceStatusHistory, DeviceChangeStatusDto>().ConvertUsing<DeviceChangeStatusConverter>();
}
}
where DeviceChangeStatusConverter
is defined as:
public class DeviceChangeStatusConverter : ITypeConverter<DeviceStatusHistory, DeviceChangeStatusDto>
{
public DeviceChangeStatusDto Convert(DeviceStatusHistory source, DeviceChangeStatusDto destination, ResolutionContext context)
{
destination = new DeviceChangeStatusDto
{
CarrierName = source.Device.CarrierId.HasValue ? source.Device.Carrier.Name : null,
DeviceId = source.DeviceId,
DateChange = source.DateChange,
DeviceIMEI = source.Device.IMEI,
DeviceName = source.Device.GetFriendlyDetailedName(),
NewStatus = CommonHelper.SplitByWords(source.NewStatus.ToString())
};
// some complex logic here
return destination;
}
}
but when I try to map it:
var list = _context.DeviceStatusHistory
.Where(a => ((int)a.NewStatus < 100) && a.DateChange.Date == date.Date)
.ProjectTo<DeviceChangeStatusDto>(_mapperConfig)
.ToList();
where _mapperConfig
is:
_mapperConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile<AutoMapperEfDeviceManagement>();
});
It maps looks like it was declared simple as :
CreateMap<DeviceStatusHistory, DeviceChangeStatusDto>();
so, only the same properties are mapped, converter is not called (debugger says the same). What is wrong?
ADDED:
approach like:
CreateMap<DeviceStatusHistory, DeviceChangeStatusDto>()
.ConvertUsing((source, destination) =>
{
destination.CarrierName = source.Device.CarrierId.HasValue ? source.Device.Carrier.Name : null;
does not work too
CodePudding user response:
TL;DR
It is not a bug, it's a known limitation. Basically AutoMapper can't convert some custom method into SQL (or translate it into the form which will allow your ORM to translate that into SQL) so it can't use your type converter in ProjectTo
.
A little bit more details:
From the documentation:
The
.ProjectTo<OrderLineDTO>()
will tellAutoMapper
’s mapping engine to emit aselect
clause to theIQueryable
that will inform entity framework that it only needs to query theName
column of theItem
table, same as if you manually projected yourIQueryable
to anOrderLineDTO
with aSelect
clause.
And from Custom Type Conversion section:
Occasionally, you need to completely replace a type conversion from a source to a destination type. In normal runtime mapping, this is accomplished via the
ConvertUsing
method. To perform the analog in LINQ projection, use theConvertUsing
method:cfg.CreateProjection<Source, Dest>().ConvertUsing(src => new Dest { Value = 10 });
The expression-basedConvertUsing
is slightly more limited thanFunc
-basedConvertUsing
overloads as only what is allowed in anExpression
and the underlying LINQ provider will work.
And Supported mapping options:
Not all mapping options can be supported, as the expression generated must be interpreted by a LINQ provider. Only what is supported by LINQ providers is supported by AutoMapper:
- MapFrom (Expression-based)
- ConvertUsing (Expression-based)
- Ignore
- NullSubstitute
- Value transformers
- IncludeMembers
Not supported:
- Condition
- SetMappingOrder
- UseDestinationValue
- MapFrom (Func-based)
- Before/AfterMap
- Custom resolvers
- Custom type converters
- ForPath
- Value converters
- Any calculated property on your domain object