Reputation: 5458
With Automapper (6.6.2) I'm trying to map a nullable boolean to a destinations object property (dest.IsEnabled.Value). But as soon the source value is null, the entire destination property (dest.IsEnabled) is null. But I only expect the property "value" to be null.
Any idea how to do it right?
class Program
{
static void Main(string[] args)
{
var source = new Source();
source.IsEnabled = null;
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Dest>();
//.ConstructUsing(ctor => new Dest());
cfg.CreateMap<bool?, IsEnabledProperty>()
.ForMember(dst => dst.Value, opt => opt.MapFrom(src => src));
});
//var debug = config.BuildExecutionPlan(typeof(Source), typeof(Dest));
var mapper = config.CreateMapper();
//var dest = mapper.Map<Source, Dest>(source, new Dest());
var dest = mapper.Map<Source, Dest>(source);
if (dest.IsEnabled == null)
{
Console.WriteLine("IsEnabled is null. But why? I expect IsEnabled.Value to be null.");
}
Console.ReadLine();
}
}
class Source
{
public bool? IsEnabled { get; set; }
}
class Dest
{
public IsEnabledProperty IsEnabled { get; set; }
= new IsEnabledProperty() { IsRequired = true };
// Just to check if the property is initialized
public OtherProperty OtherProperty { get; set; }
= new OtherProperty() { IsRequired = true };
}
class IsEnabledProperty
{
public bool IsRequired { get; set; }
public bool? Value { get; set; }
}
class OtherProperty
{
public bool IsRequired { get; set; }
public bool? Value { get; set; }
}
Update
When I update the mapping like this
cfg.CreateMap<bool?, IsEnabledProperty>()
.ConvertUsing((src, dst, context) =>
{
if (dst != null)
dst.Value = src;
return dst;
});
dest.IsEnabled.Value is mapped correctly for all variants (null, false, true), and also the property destination.IsEnabled.IsRequired has the initialized value of true, but it forces me to pass an instance of destination to the mapping method: var dest = mapper.Map<Source, Dest>(source, new Dest());
.
This doesn't make sense in my eyes, since I expect the destination object should be constructed the same way by automapper?
When looking at the execution plan, I'm asking me, why it checks for (dest == null)
in line 11 and not just use the initialized typeMapDestination
:
(src, dest, ctxt) =>
{
Dest typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new Dest();
try
{
var resolvedValue = ((src == null) || false) ? null : src.IsEnabled;
var propertyValue = mappingFunction.Invoke(resolvedValue, (dest == null) ? null : typeMapDestination.IsEnabled, ctxt);
typeMapDestination.IsEnabled = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
};
return typeMapDestination;
};
}
Upvotes: 1
Views: 918
Reputation: 284
I think you want to map the whole bool?
instead of only the value dest.IsEnabled.Value
. This way you can ask the boolean if it has a value with dest.HasValue
and use the value with dest.Value
.
You might want to add AllowNullableMapping
to the Automapper configuration.
Upvotes: 1