How to ignore null values for all source members during mapping in Automapper 6?

C#MappingAutomapper

C# Problem Overview


I've been looking everywhere: stackoverflow, automapper documentation, internets and just couldn't find any info on this one, even tho this seems to be a very common problem.

My mapping:

CreateMap<StatusLevelDTO, StatusLevel>()
            .ForAllMembers(opt => opt.Condition(src => src != null));

This doesn't work because src represents source object (StatusLevelDTO), not a source property (I think).

To be more specific, If I map ObjectA to ObjectB, ObjectA.SomeValue is null and ObjectB.SomeValue is 2, I want the destination object to keep its value (2).

I've seen this question: https://stackoverflow.com/questions/20021633/automapper-skip-null-values-with-custom-resolver and tried the first two answers but they both seem to be outdated for version 6.

Is there any way to make this happen in Automapper 6? I am using 6.0.2 to be exact.

C# Solutions


Solution 1 - C#

Method Condition now has five overloads, one of which accepts predicate of type

Func<TSource, TDestination, TMember, bool>

this TMember parameter is the source member. So you can check source member for null:

CreateMap<StatusLevelDTO, StatusLevel>()
     .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));

Solution 2 - C#

This might be late, but for those who are still looking, this might solve your problem the same as mine.

I agree with @sergey to use:

CreateMap<StatusLevelDTO, StatusLevel>()
    .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));

But mapping nullable to non nullable will be an issue like int? to int it will always return 0. to fix it you can convert int? to int in mapping.

CreateMap<int?, int>().ConvertUsing((src, dest) => src ?? dest);
CreateMap<StatusLevelDTO, StatusLevel>()
     .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));

Solution 3 - C#

The solution here works for my project, which is using AutoMapper 6.0.2. In previous projects using AutoMapper 4, I had used IsSourceValueNull to achieve the same behavior.

I made a small change to the original solution. Instead of checking the type of the property to be mapped, I set the filter in ForAllPropertyMaps to check the type of the source object, so that the custom resolver only applies to maps from that source object. But the filter can be set to anything as needed.

var config = new MapperConfiguration(cfg =>
{
	cfg.ForAllPropertyMaps(
		pm => pm.TypeMap.SourceType == typeof(<class of source object>),
		(pm, c) => c.ResolveUsing<object, object, object, object>(new IgnoreNullResolver(), pm.SourceMember.Name));
});

class IgnoreNullResolver : IMemberValueResolver<object, object, object, object>
{
	public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
	{
		return sourceMember ?? destinationMember;
	}
}

Solution 4 - C#

I inspired from @Sergey Berezovskiy's answer, and made this configuration for all members of all maps in the main config:

Mapper.Initialize(cfg =>
{
  cfg.ForAllMaps((obj, cnfg) => cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
}

Solution 5 - C#

As I don't have the reputation to comment, I'll add my answer down here for @Sikor @sensei

If you're using a Model that has the nullable data types of your DTO you can use this extension method below to negate the effects of Automapper resorting to the particular data type's default value.

Model examples

public class Foo {
    public bool? Example { get; set; }
}

public class FooDto {
    public bool Example { get; set; }
}

Extension Method:

public static TTarget MapModelProperties<TTarget, TSource>(this TTarget target, TSource source) where TTarget : class
																								where TSource : class

	{
		// Map target into the source, where the source property is null
		Mapper.Initialize(cfg =>
		{
			cfg.CreateMap<TTarget, TSource>()
				.ForAllMembers(opt => opt.Condition((src, dest, srcMember, destMember) => destMember == null));
		});
		Mapper.Map(target, source);

		// Map the source into the target to apply the changes
		Mapper.Initialize(cfg => cfg.CreateMap<TSource, TTarget>());
		Mapper.Map(source, target);

		return target;
	}

Usage

public class Foo
{
	public bool? Example { get; set; }
}

public class FooDto
{
	public bool Example { get; set; }
}

public void Example()
{
	var foo = new Foo
	{
		Example = null
	};

	var fooDto = new FooDto
	{
		Example = true
	};

	fooDto.MapModelProperties(foo);
}

This maps the Dto property values into all model's property values that are null. Then maps the model property values back into the Dto, thus only changing the Dto values that are present in the model.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSikorView Question on Stackoverflow
Solution 1 - C#Sergey BerezovskiyView Answer on Stackoverflow
Solution 2 - C#CherryBlossomView Answer on Stackoverflow
Solution 3 - C#TimView Answer on Stackoverflow
Solution 4 - C#Farid ImranovView Answer on Stackoverflow
Solution 5 - C#tw1tch01View Answer on Stackoverflow