Mr Giggles
Mr Giggles

Reputation: 2104

Automapper ignore all mapping where value is null or default value

I'm using automapper via DI and want to have generic rules for all the mappings found in my solution. Belows example is of a single class, however I've got hundreds of classes to maintain and therefore want to add it to the mapper, not the mapping profile. So far, I can handle Null values as follows :

 cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));

Which in my example below, allows for the null update to persist past all the subsiquent updates. What I want is a generic rule ( or statement) for the default value of Guids, DateTimeOffset and Int. In my example below, update 1 and 2 are lost once update 3 is mapped.

using System;
using Autofac;
using AutoMapper;

namespace AutoMapperProblem
{
    public class Program
    {
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();

        builder.RegisterInstance(new MapperConfiguration(cfg =>
        {
            cfg.AddMaps(typeof(Program).Assembly);
            cfg.ForAllMaps((obj, cnfg) =>
                cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));

        }).CreateMapper()).As<IMapper>().SingleInstance();

        var container = builder.Build();

        var mapper = container.Resolve<IMapper>();

        var MainObject = new MainObject();

        //this update persists pass all updates
        var updateNull = new UpdateObject { NullGuid = Guid.NewGuid() };

        mapper.Map(updateNull, MainObject);

        var update1 = new UpdateObject { DateTimeOffset = DateTimeOffset.Now };

        mapper.Map(update1, MainObject);

        var update2 = new UpdateObject { Guid = Guid.NewGuid() };

        mapper.Map(update2, MainObject);

        var update3 = new UpdateObject { Int = 10 };

        mapper.Map(update3, MainObject);
    }
}

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<MainObject, UpdateObject>()
            .ReverseMap();
    }
}

public class MainObject
{
    public Guid? NullGuid { get; set; }
    public Guid Guid { get; set; }
    public int Int { get; set; }
    public DateTimeOffset DateTimeOffset { get; set; }
}

public class UpdateObject
{
    public Guid? NullGuid { get; set; }
    public Guid Guid { get; set; }
    public int Int { get; set; }
    public DateTimeOffset DateTimeOffset { get; set; }

}
}

I want it to work based on the type, eg ints, DateTimeOffsets and Guid's .

Heres a working example on DotNetFiddle : https://dotnetfiddle.net/Zh0ta6

Thanks for any help with this

Upvotes: 2

Views: 2713

Answers (1)

Progman
Progman

Reputation: 19555

The problem is that you cannot check value types against null in your opts.Condition() check, as they always have a value. Also, you can't use the default keyword directly in this specific context because the variable type is object. That will turn default to null again, and fail.

Depending on what you want to do, you can check if the destination already have a non-default value and do NOT replace it with any source value, which might be the default value of the type (0 for int, 0000...-000 for Guid, etc.). The check could look like this:

cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember, destMember) =>
{
    object destDefaultValue = null;
    if (destMember != null)
    {
        Type destType = destMember.GetType();
        if (destType.IsValueType)
        {
            destDefaultValue = Activator.CreateInstance(destType);
        }
        else
        {
            destDefaultValue = null;
        }
    }

    bool destinationHasNoValue = Object.Equals(destMember, destDefaultValue);
    return destinationHasNoValue;
})

The first part is calculating the default value for the given destination value, based on the type of the destination value. The latter part is checking if the destination already have a non-default value or not. Consecutive calls of Map() with the same target object will fill the "missing" property values until all the property values are set (in this example). If necessary, check the srcMember value and it's default type as well to fine-tune when the value should be copied or not.

Upvotes: 1

Related Questions