简体   繁体   中英

AutoMapper ConvertUsing is not called

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

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 thedocumentation :

The .ProjectTo<OrderLineDTO>() will tell AutoMapper 's mapping engine to emit a select clause to the IQueryable that will inform entity framework that it only needs to query the Name column of the Item table, same as if you manually projected your IQueryable to an OrderLineDTO with a Select 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 the ConvertUsing method: cfg.CreateProjection<Source, Dest>().ConvertUsing(src => new Dest { Value = 10 });
The expression-based ConvertUsing is slightly more limited than Func -based ConvertUsing overloads as only what is allowed in an Expression 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM