75

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: 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.

8 Answers 8

128

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));
Sign up to request clarification or add additional context in comments.

18 Comments

Actually, I've tried something similar before posting this question. The only difference was that I used 4 params, like this: (src, dest, srcMember, dstMember) and it didn't work. I've checked again with your version and it still doesn't work.
This is interesting. When I call Mapper.Map(updatedStatusLevel, level); 2 fields in the destination object change and they shouldn't. One changes from 2 to 0 (int type) and the other changes from list with 4 entries to null because both of these fields are null in the source object.
Yeah, looks like it's working for you. I think the only difference is that I am debugging a unit test. I will try again running the whole application. Maybe something is not right with the tests.
@Sikor seems like you have int? in source object and int in destination. When AutoMapper tries to map this field it checks whether destination is nullable and uses default int value as sourceMember value. That's why null check condition fails and this default value is assigned to destination object
@Sikor similar stuff - AutoMapper by default does not preserve null values for collections. There is setting cfg.AllowNullCollections = true to disable such behavior
|
40

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));

3 Comments

Lifesaver 👍👍👍
After trying so many options I was stuck where my Nullable DateTime was still being mapped to DateTime.MinValue (0001-01-01) and I was not able to exclude that property when its value was null. Mapping DateTime? to DateTime just like you did with "int" worked for me.
CreateMap<int?, int>().ConvertUsing((src, dest) => src ?? dest); doesnt work for when the property names don't match. I dont have a solution for that
9

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;
    }
}

1 Comment

Need some changes to use in AutoMapper 8.1.1: cfg.ForAllPropertyMaps(pm => pm.TypeMap.SourceType == typeof(<class of source object>), (pm, c) => c.MapFrom(new IgnoreNullResolver(), pm.SourceMember.Name))
8

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)));
}

Comments

3

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.

1 Comment

did you solve that? I am having same issue
1

Seven years late, but for those who face this issue with collection type properties use:

public class SomeContainerDto
{
    public StatusLevelDto[] statusLevels { get; set;}

    public string Foo { get; set;}

    public string Bar { get; set;}
}

public class SomeContainer
{
    public StatusLevel[] statusLevels { get; set;}

    public string Foo { get; set;}

    public string Bar { get; set;}
}

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

This is automapper 12.0.1

1 Comment

Yes this is exactly what I was looking for, thanks!
0

This solution allows ignoring nulls including destination properties that are non-nullable

/// <summary>
        /// Extension method for the <see cref="IMappingExpression" /> that causes the mapping to not attempt to map null properties on the Source to the Destination including those properties that are non-nullable types on the Destination
        /// This method was created because there is no easy way to configure AutoMapper not to map null values on the Source object when the Destination object has non-nullable types on it. The default behavior of
        /// Automapper is to set non-nullable destination types to its Default if the Source value is null. The IgnoreNullsOnSource method overrides this default behavior.
        /// </summary>
        /// <typeparam name="TSource">Source object - null values on this object will not be mapped to the Destination</typeparam>
        /// <typeparam name="TDestination">Destination object</typeparam>
        /// <param name="mappingExpression">The <see cref="IMappingExpression"/> that will be configured to ignore null values on the source</param>
        public static IMappingExpression<TSource, TDestination> IgnoreNullsOnSource<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression)
        {
            mappingExpression.IsRequired();

            foreach (PropertyInfo destinationProperty in typeof(TDestination).GetProperties())
            {
                mappingExpression.ForMember(destinationProperty.Name,
                    propertyMap => propertyMap.Condition((source, _, _, _, resolutionContext) =>
                    {
                        TypeMap typeMap = resolutionContext.ConfigurationProvider.ResolveTypeMap(typeof(TSource), typeof(TDestination));
                        PropertyMap propertyMap = typeMap.FindOrCreatePropertyMapFor(destinationProperty);
                        if (propertyMap.HasSource() && propertyMap.SourceMember is PropertyInfo sourceProperty)
                        {
                            object? originalSourceValue = sourceProperty.GetValue(source);
                            bool shouldPerformMapping = originalSourceValue != null;
                            return shouldPerformMapping;
                        }
                        return true; // the default is to map all values
                    }));
            }

            return mappingExpression;
        }

And then call it like this

CreateMap<Source, Dest>()
   .IgnoreNullsOnSOurce()

Comments

-1

Here is a pretty simple workaround

public static class ProfileExtensions
{
    public static IMappingExpression<TSource, TDestination> CreateMapIgnoringNullSourceMember<TSource, TDestination>(this Profile profile)
    {
        profile.CreateMap<TDestination, TSource>()
            .ForAllMembers(x => x.Condition((src, dest, srcMember, destMember) =>
            {
                return destMember == null;
            }));

        return profile.CreateMap<TSource, TDestination>()
            .BeforeMap((src, dest, context) =>
            {
                context.Mapper.Map(dest, src);
            });
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.