I am using Automapper for mapping DB entities from entity framework to business objects through ProjectTo<>()
. Now I'd like to map/project nullable decimal to class type and don't want to specify manual mapping of these two types for every property in each object so I decided to use a custom type converter and the Automapper will do the work. When a decimal?
property has value it works fine but when it is null (and the DB table column allows NULLs) it throws an exception saying "Null TypeMapping in Sql Tree" and the converter's Convert()
method is not called at all. The code is as follows:
basic class type and derived (one of many)
public abstract class BaseUnit
{
public decimal? DefaultValue { get; protected set; }
public abstract ViewableUnit GetRequestedUnit(Unit unit);
}
public class Density : BaseUnit
{
public Density()
{
this.DefaultValue = decimal.Zero;
}
public Density(decimal? densityInGperL)
{
this.DefaultValue = densityInGperL;
}
public decimal? GramsPerLiter
{
get => this.DefaultValue;
set => this.DefaultValue = value;
}
public decimal? GramsPerDeciliter
{
get => this.DefaultValue * 10;
set => this.DefaultValue = value * 0.1m;
}
public override ViewableUnit GetRequestedUnit(Unit unit)
{
...
}
}
converter and registration
public class DecimalUnitTypeConverter : ITypeConverter<decimal?, Density>
{
public Density Convert(decimal? source, Density destination, ResolutionContext context)
{
return source.HasValue ? new Density(source.Value) : null;
}
}
Mapper.CreateMap(typeof(decimal?), typeof(Density)).ConvertUsing(typeof(DecimalUnitTypeConverter));
DB entity and DTO
public class LabResult
{
decimal? NumberResult { get; set; }
...
}
public class LabResultDto
{
Density NumberResult { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<LabResult, LabResultDto>()
.ForMember(
dst => dst.NumberResult,
opt =>
{
opt.PreCondition(src => src.NumberResult.HasValue); // does not have to be here, the outcome is the same
opt.MapFrom(src => src.NumberResult);
});
}
}
and final usage
dbContext.LabResults.AsNoTracking()
.Where(lab => lab.Id == request.Id)
.ProjectTo<LabResultDto>(this.configurationProvider)
.ToList();
I know that Map and ProjectTo work differently but I thought that this type of mapping / projection is trivial even if I tell the Automapper how to do it via type converter. Am I missing something? Thanks for any kind of help.
As type converters do not work for projection as @Lucian Bargaoanu posted, I found a solution. Maybe not suitable generally for everyone but for my purpose it works quite well. I just declared ordinary mapping from decimal?
to any of my classes I want the mapping to be applied to and Automapper will do the rest.
this.CreateMap<decimal?, Density>()
.ForMember(
dst => dst.GramsPerLiter,
opt => opt.MapFrom(src => src));
Automapper creates instance of Density class using the default ctor and then assigns the value through "default" property. In this case the property GramsPerLiter
serves as a default property where all density-like records are uniformly mapped to throughout the system and, if needed, are converted into different unit (kg/L, g/cm3, kg/m3 ...).