I have the following issue using ASP.NET Core 2.2 and Automapper 9.0.0:
I have a Entity that I map to a dto, this works fine. Inside that entity are a few entities as well. These cannot be flattened as they are also needed by our client. There is however one property within those nested entities that need additional mapping.
within the Profile class of the overlapping Entity i do this:
public class OperationalToPalletResultProfile : Profile
{
public OperationalToPalletResultProfile()
{
string lang = null;
CreateMap<TblDatOperationalHUTrace, PalletResult>()
.ForMember(dest => dest.TransCode, act => act.MapFrom((src, _, transactionCode, ctx) =>
ctx.Mapper.Map<TblLstCode, TransactionCodeResult>(src.TransCode)));
}
}
This effectively maps the TblLstCode to TransactionCodeResult correctly. However the "DisplayDesc" property of the TransactionCodeResult remains null...
Note: the "lang" property gets set with the "ProjectTo" method:
var values = await query
.Take(5000)
.ProjectTo<PalletResult>(mapper.ConfigurationProvider, new { lang = language })
.ToListAsync()
.ConfigureAwait(false);
I have tried:
1- Using "AfterMap" in Profile class (result: DisplayDesc = null)
CreateMap<TblDatOperationalHUTrace, PalletResult>()
.ForMember(dest => dest.TransCode, act => act.MapFrom((src, _, transactionCode, ctx) =>
ctx.Mapper.Map<TblLstCode, TransactionCodeResult>(src.TransCode, opt =>
opt.AfterMap((source, destination) =>
destination.DisplayDesc = $"[{destination.ShortName}] {destination.DescToLanguageDesc(destination, lang)}"))))
Note: DescToLanguageDesc is a function that finds a property in the entity with reflection (this works and is not part of the problem)
2- Creating an IMappingAction to use in the Profile of the SubEntity (result: DisplayDesc = null)
public class TransactionCodeTranslation : IMappingAction<TblLstCode, TransactionCodeResult>
{
private readonly IHttpContextAccessor httpContextAccessor;
public TransactionCodeTranslation(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Process(TblLstCode source, TransactionCodeResult destination, ResolutionContext context)
{
//Get language from httpContext - this works correctly
destination.DisplayDesc = $"[{source.ShortName}] {source.DescToLanguageDesc(source, language)}";
}
}
public class TransactionCodeProfile : Profile
{
public TransactionCodeProfile()
{
string language = string.Empty;
CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
}
}
This doesn't work. HOWEVER if I use option 2 directly:
var tc = await codeRepo.TableNoTracking.FirstOrDefaultAsync(x => x.ShortName == "[something]").ConfigureAwait(false);
var transactionCode = mapper.Map<TblLstCode, TransactionCodeResult>(tc);
Then it works correctly! But that would mean i would have to loop my result and map every object in the result again...
Is there a way to do it like option 1?
Thank you!
Edit 1:
Per Lucian Bargaoanu's request i added a BuildExecutionPlan:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TblDatOperationalHUTrace, PalletResult>()
.ForMember(dest => dest.TransCode, act => act.MapFrom((src, _, transactionCode, ctx) =>
ctx.Mapper.Map<TblLstCode, TransactionCodeResult>(src.TransCode, opt =>
opt.AfterMap((source, destination) =>
destination.DisplayDesc = $"[{destination.ShortName}] {destination.DescToLanguageDesc(destination, language)}"))));
cfg.CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
});
var expression = config.BuildExecutionPlan(typeof(TblDatOperationalHUTrace), typeof(PalletResult));
var expression2 = config.BuildExecutionPlan(typeof(TblLstCode), typeof(TransactionCodeResult));
Result of expression:
(src, dest, ctxt) =>
{
PalletResult typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new PalletResult();
try
{
var resolvedValue = mappingFunction.Invoke(
src,
typeMapDestination,
typeMapDestination.TransCode,
ctxt);
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.TransCode = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
return typeMapDestination;
};
}
Result Expression2:
(src, dest, ctxt) =>
{
TransactionCodeResult typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new TransactionCodeResult();
try
{
var resolvedValue = ((src == null) || false) ? default(int) : src.Id;
typeMapDestination.Id = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(int);
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.ShortName;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.ShortName = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? default(int) : src.CodeTypeId;
typeMapDestination.CodeTypeId = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(int);
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescLC;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescLC = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescEN;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescEN = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescFR;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescFR = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescGE;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescGE = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescNL;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescNL = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
afterFunction.Invoke(src, typeMapDestination, ctxt);
return typeMapDestination;
};
}
EDIT 2:
Ok, I actually found the solution. I was overcomplicating things for no reason...
I changed OperationalToPalletResultProfile to:
public class OperationalToPalletResultProfile : Profile
{
public OperationalToPalletResultProfile()
{
CreateMap<TblDatOperationalHUTrace, PalletResult>();
}
}
The TransactionCodeProfile remained the same:
public class TransactionCodeProfile : Profile
{
public TransactionCodeProfile()
{
CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
}
}
And then where i use my mapping i changed this:
//results as TblDatOperationalHUTrace
var queryresult = await query
.Take(5000)
//.ProjectTo<PalletResult>(mapper.ConfigurationProvider) Don't do the mapping here anymore
.ToListAsync()
.ConfigureAwait(false);
//Map results to PalletResult
var values = mapper.Map<List<TblDatOperationalHUTrace>, List<PalletResult>>(queryresult); //Do the mapping here