I missed some piece of code
You have not missed anything.
or is this a bug in Automapper and should I report this issue to an autopilot answering machine? Or it is possible "by design", but why?
I doubt that this is “by design,” most likely a mistake or an incomplete quick and dirty implementation. This can be seen inside the source code of the ApplyInheritedPropertyMap method of the PropertyMap class, which is responsible for combining the base property and derived properties. “Inherited” display properties currently:
CustomExpressionCustomResolverConditionPreConditionNullSubstituteMappingOrderValueResolverConfig
while the following (basically all bool types) (including this question) are not:
AllowNullUseDestinationValueExplicitExpansion
The IMO problem is that the current implementation cannot determine if the bool property is set explicitly or not. Of course, it can be easily fixed by replacing the automatic properties with the explicit bool? field bool? and the logic of the default values (and an additional free way to configure it to disable it if it is enabled inside the base class configuration). Unfortunately, this can only be done in the source code, so I would advise you to report this problem to your tracker.
Before (and if) they fix this, I could suggest, as a workaround, moving all the common code to custom extension methods, like
static class MyMappers { public static IMappingExpression<TSource, TDestination> Configure<TSource, TDestination>(this IMappingExpression<TSource, TDestination> target) where TSource : Source where TDestination : DtoBase { return target .ForMember(dto => dto.Name, conf => { conf.MapFrom(src => src.Name); conf.ExplicitExpansion(); }); } }
and use them from the main configuration code:
Mapper.Initialize(cfg => { cfg.CreateMap<Source, DtoBase>() .Configure(); cfg.CreateMap<Source, DtoDerived>() .Configure() .ForMember(dto => dto.Desc, conf => { conf.MapFrom(src => src.Desc); conf.ExplicitExpansion(); }); });
Edit: Concerning additional issues. Both are more serious non-configuration AM processing errors.
The problem is that they are trying to use MemberInfo instance comparison to filter projection.
The first case (with an expression) fails for value types because an implementation that tries to retrieve MemberInfo from Expression<Func<T, object>> expects only MemberExpression , but in the case of value types, it terminates inside Expression.Convert .
The second case (with property names) fails because they do not take into account the fact that MemberInfo for a property inherited from the base class extracted from the compilation-time lambda expression is different from that obtained by reflecting or executing the created expression, which demonstrated with the following test:
// From reflection var nameA = typeof(DtoDerived).GetMember(nameof(DtoDerived.Name)).Single(); // Same as //var nameA = typeof(DtoDerived).GetProperty(nameof(DtoDerived.Name)); // From compile time expression Expression<Func<DtoDerived, NameDtoType>> compileTimeExpr = _ => _.Name; var nameB = ((MemberExpression)compileTimeExpr.Body).Member; // From runtime expression var runTimeExpr = Expression.PropertyOrField(Expression.Parameter(typeof(DtoDerived)), nameof(DtoDerived.Name)); var nameC = runTimeExpr.Member; Assert.AreEqual(nameA, nameC); // Success Assert.AreEqual(nameA, nameB); // Fail
You definitely need to report all problems. I would say that a function is compromised for any property of type value when delivering a list of expressions and for any inherited property when delivering names.