SolvedAutoMapper Nullable properties on source being cast before conditional function called

I'm writing a restful api and in my use case I am trying to map a nullable type on my source to a non-nullable property on my destination. I want it to ignore mapping properties if the source value is null. I want this because it is a really elegant solution for me to deserialize a partial json request into my object and only update fields on my entity object that were actually passed to my dto. See my code below (note, I've removed all my Entity Framework junk).

These are my source (ScriptDto) and destination (Script) classes.

public class ScriptDto
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        [JsonConverter(typeof(StringEnumConverter))]
        public ScriptLanguage? Language { get; set; }
        public string Target { get; set; }
        public string ScriptBody { get; set; }
        public DateTime? LastExecuted { get; set; }
        public Guid? LastExecutedBy { get; set; }
    }

 public class Script : IEntity
    {
        public Guid Id { get; private set; }
        public string Name { get; set; }
        public ScriptLanguage Language { get; set; }
        public string Target { get; set; }
        public string ScriptBody { get; set; }
        public DateTime LastExecuted { get; set; }
    }

This is my web api routed method

[HttpPut("{id}")]
public IActionResult UpdateScript(string id, [FromBody]ScriptDto scriptDto, [FromServices]IDatabaseConfigurationLoader loader)
   {
                Script script = //code to get my script object from the database
                if (script == null) { return NotFound(); }

                 var config = new MapperConfiguration(cfg => {
                    cfg.CreateMap<ScriptDto, Script>()
                       .ForMember(y => y.Id, opt => opt.Ignore())
                       .ForAllOtherMembers(opt =>
                       {
                           opt.Condition((src, dest, srcValue) =>
                           {
                               return srcValue != null; //ignore properties on dto that are null
                           });
                       });
                   });

                _mapper = config.CreateMapper();
                //map dto to entity
                _mapper.Map(dto, script);

                //save changes to database

                return new ObjectResult(MapToDto(script)); //return updated object
   }

What I would expect to happen with this, is if the source property is null the condition function evaluates to false and the property mapping is skipped. That is not what is happening for the nullable properties on my source object. After setting a break point inside the conditional function I realized that my source property values were being cast to the destination type (I assume) before they are passed to the conditional function. So where I would expect "srcValue" to be null it was actually already cast to its non-nullable default. If for example, I sent the following json PUT request to my service at api/v1/scripts/somescriptid

{ "scriptBody": "test body" }

then it would deserialize into a DTO object with mostly null properties, with the exception of the ScriptBody property. But when it gets mapped to my destination object, the DateTime? LastExecuted property which is null (for example) has its value cast to a non-nullable DateTime prior to being passed to the conditional function. Which of course causes my conditional function to evaluate to true and thus the destination property is overwritten.

I imagine I am having this issue because I am trying to map a nullable property on my source to a non-nullable property on my destination. When I change the property on my destination object to a nullable type then it works the way I would expect. That said, I do not want nullable types where they aren't required on my entity object. I'm using the latest dev build on myget, 5.2.0-alpha-01223.

49 Answers

✔️Accepted Answer

You can check against the default value for the value type, but that's not exposed right now through the interface, so you need a cast.
Update : no need for a cast anymore.

        cfg.CreateMap<Source, Destination>()
                .ForAllOtherMembers(opt =>opt.IgnoreSourceWhenDefault());

public static class Extensions
{
    public static void IgnoreSourceWhenDefault<TSource, TDestination>(this IMemberConfigurationExpression<TSource, TDestination, object> opt)
    {
        var destinationType = opt.DestinationMember.GetMemberType();
        object defaultValue = destinationType.GetTypeInfo().IsValueType ? Activator.CreateInstance(destinationType) : null;
        opt.Condition((src, dest, srcValue) =>!Equals(srcValue, defaultValue));
    }
	
    public static Type GetMemberType(this MemberInfo memberInfo)
    {
       if (memberInfo is MethodInfo)
           return ((MethodInfo)memberInfo).ReturnType;
       if (memberInfo is PropertyInfo)
           return ((PropertyInfo)memberInfo).PropertyType;
       if (memberInfo is FieldInfo)
           return ((FieldInfo)memberInfo).FieldType;
       return null;
    }
}

Other Answers:

I can't check for the default value, because the default value is a legitimate value. That's why the property is Nullable. I need to prevent mapping null values for Nullable properties from the source. Note that this only seems to occur when mapping a Nullable to a non-Nullable (int? to int in my case). Nullable to Nullable works.

Related Issues:

3
AutoMapper Nullable properties on source being cast before conditional function called
You can check against the default value for the value type but that's not exposed right now through ...