-1

AutoMapper issue #3962 was closed and locked without explanation or resolution. (In fact, all AutoMapper issues are being treated this way. The maintainer is directing people here.)

To summarize the issue, IMemberConfigurationExpression.Condition receives a Func<TSrc, TDest, object, bool>. The third parameter is the value of the source member being visited. For example:

public static class IMappingExpressionExtensions
{
    public static void Coalesce<Src, Dst>(this IMappingExpression<Src, Dst> exp)
    {
        exp.ForAllMembers(cfg => cfg.Condition((src, dst, srcMember) => srcMember != null));
    }
}

When the source member is a reference type and its value is null, srcMember is null as expected. But when the source member is a Nullable<T> without a value, srcMember is unexpectedly default(T) instead of null.

Is there a workaround?

Progman
  • 16,827
  • 6
  • 33
  • 48
Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82
  • @Progman Done. I just found [another closed issue](https://github.com/AutoMapper/AutoMapper/issues/2999) that references [another Q&A](https://stackoverflow.com/questions/55137405/how-to-set-automapper-ignore-null). Apparently this is a common gotcha. Still looking into whether my requirements are the same and whether any of those solutions work. – Kevin Krumwiede Aug 13 '22 at 18:24
  • And [another closed issue](https://github.com/AutoMapper/AutoMapper/issues/1703). This is apparently a very common developer expectation, but the AutoMapper devs won't meet the expectation because it would be a breaking change. – Kevin Krumwiede Aug 13 '22 at 18:40
  • It's becoming clear that the developer [doesn't understand the problem](https://github.com/AutoMapper/AutoMapper/issues/1703#issuecomment-265411448) and enjoys throwing people crumbs and being dismissive. – Kevin Krumwiede Aug 13 '22 at 19:17

1 Answers1

0

The heart of the matter is that Condition is called after the source member has been converted to the destination member type. This is why Condition works as expected for reference types, and even for Nullable<T> to Nullable<T>, but not Nullable<T> to T.

If all properties of TSrc map to properties of TDst with the same names, the following solution completely replaces Condition, producing the expected behavior for reference to reference, Nullable<T> to Nullable<T>, and Nullable<T> to T. Despite involving reflection and boxing, it seems to be quite fast.

public static void Coalesce<TSrc, TDst>(this IMappingExpression<TSrc, TDst> mapExp)
{
    foreach (var srcProp in typeof(TSrc).GetProperties())
    {
        mapExp.ForMember(srcProp.Name, mapExp => mapExp.PreCondition((o, _) => srcProp.GetValue(o) != null));
    }
}

If that doesn't work for you, and you can live with changing the mapping of Nullable<T> to T for all mappings, you can replace the default type converter for <T?,T> with one that "converts" a null Nullable<T> into the destination value.

public class CoalescingTypeConverter<T> : ITypeConverter<T?, T> where T : struct
{
    public T Convert(T? source, T destination, ResolutionContext context)
    {
        return source ?? destination;
    }
}
public static class IMapperConfigurationExpressionExtensions
{
    public static void CoalesceNullable<T>(this IMapperConfigurationExpression cfg) where T : struct
    {
        cfg.CreateMap<T?, T>().ConvertUsing<CoalescingTypeConverter<T>>();
    }
}
services.AddAutoMapper(cfg =>
{
    cfg.CoalesceNullable<int>();
    cfg.CoalesceNullable<bool>();
    // etc.
}
Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82