15

I want to use automapper to map between my public data contracts and my DB models. I have created a class which automatically goes through all the contracts are creates mappings. The only problem I have is that I only want to map values from the contract to the DB model if the value is not null. I have looked at other question on here but cant see examples that use custom resolvers.

Here is some of my code

var mapToTarget = AutoMapper.Mapper.CreateMap(contract, mappedTo);
foreach (var property in contract.GetProperties().Where(property => property.CustomAttributes.Any(a => a.AttributeType == typeof(MapsToProperty))))
{
  var attribute = property.GetCustomAttributes(typeof(MapsToProperty), true).FirstOrDefault() as MapsToProperty;

  if (attribute == null) continue;

  mapToTarget.ForMember(attribute.MappedToName,
                    opt => 
                        opt.ResolveUsing<ContractToSourceResolver>()
                            .ConstructedBy(() => new ContractToSourceResolver(new MapsToProperty(property.Name, attribute.SourceToContractMethod, attribute.ContractToSourceMethod))));
}


private class ContractToSourceResolver : ValueResolver<IDataContract, object>
{
  private MapsToProperty Property { get; set; }

  public ContractToSourceResolver(MapsToProperty property)
  {
     Property = property;
  }

  protected override object ResolveCore(IDataContract contract)
  {
     object result = null;
     if (Property.ContractToSourceMethod != null)
     {
         var method = contract.GetType()
                    .GetMethod(Property.ContractToSourceMethod, BindingFlags.Public | BindingFlags.Static);
          result = method != null ? method.Invoke(null, new object[] {contract}) : null;
      }
      else
      {
         var property =
                    contract.GetType().GetProperties().FirstOrDefault(p => p.Name == Property.MappedToName);
         if (property != null)
         {
             result = property.GetValue(contract);
         }
      }

      return result;
   }
}

And this is how I want to use it

AutoMapper.Mapper.Map(dataContract, dbModel)

This currently works great but if there is a NULL value in the dataContract then it will replace the existing value in the dbModel, this is not what I want. How do I make AutoMapper ignore null source values?

EDIT

As pointed out in one of the answers there is this

Mapper.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));

This would be ideal except for the fact that .ForAllMembers is not accessible from

Mapper.CreateMap(SourceType, DestinationType)
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Mike Norgate
  • 2,393
  • 3
  • 24
  • 45
  • Possible duplicate of [AutoMapper.Map ignore all Null value properties from source object](https://stackoverflow.com/questions/12514084/automapper-map-ignore-all-null-value-properties-from-source-object) – Michael Freidgeim Apr 10 '18 at 13:24

4 Answers4

30

UPDATE: IsSourceValueNull is not available starting from V5.

If you want all source properties with null values to be ignored you could use:

Mapper.CreateMap<SourceType, DestinationType>()
  .ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));

Otherwise, you can do something similar for each member.

Read this.

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
Prasad Kanaparthi
  • 6,423
  • 4
  • 35
  • 62
  • I've already looked at this and it does not work for the way I'm creating a map. The ForAllMembers is only available when creating a map using CreateMap<>() and you can see from my example I'm not using the generic method as I need to use variables to specify the types. – Mike Norgate Nov 17 '13 at 09:13
  • 1
    Hi, can we configure the condition for all classes automatically? Thanks. – stenlytw Sep 17 '15 at 11:01
  • @bounces, That is good idea. I am not sure about that. My guess you can use reflection and get all classes and foreach class you can do above logic. – Prasad Kanaparthi Sep 21 '15 at 05:49
  • 2
    Am using automapper 6.0.2. the extension IsSourceValueNull does not exist – flexxxit Apr 28 '17 at 08:14
  • I added an answer that works for my project which is using AutoMapper 6.0.2. – Tim Jul 08 '17 at 13:20
15

For newer versions that utilize the Instance API, use this instead:

var mappingConfig = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(
        (source, destination, sourceMember, destMember) => (sourceMember != null)));
});

Note: This functionality works as of 5.0.2, breaking on later versions as of this writing. Waiting for the next 5.2.x release is recommended if upgrading from 5.0.2.

Marc Lopez
  • 561
  • 8
  • 15
  • 1
    This doesn't work on 6.1.1 if you're using Nullable to non-nullable T. E.g. Nullable => int - it thinks that the source value is 0 if it's null. – Zac Faragher Jul 03 '17 at 00:58
  • I added an answer that works for my project which is using AutoMapper 6.0.2. – Tim Jul 08 '17 at 13:20
5

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 similar behavior. I have nullable types mapped to non-nullable types in my project, this solution is able to handle that scenario.

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;
    }
}
Tim
  • 1,344
  • 1
  • 11
  • 12
2

I've got the same exact issue with mapping up the conditionals to the non-generic types. Here's how I solved it.

Wire up:

foreach (PropertyInfo p in type.GetProperties().Where(x => x.GetCustomAttributes<SkipMapIfNullAttribute>().Any()))
    map.ForMember(p.Name, x => x.ResolveUsing(typeof(SkipMapIfNullResolver)).FromMember(p.Name));

The second .FromMember is required so the value for the member is passed to the value resolver, rather than the full model.

The resolver looks like this:

public class SkipMapIfNullResolver : IValueResolver
{
    public ResolutionResult Resolve(ResolutionResult source)
    {
        if (source.Value == null)
            source.ShouldIgnore = true;

        return source;
    }
}
Chris Hynes
  • 9,999
  • 2
  • 44
  • 54
  • This works great with a IValueResolver, it doesn't work if you try to use the Func. Use source.New(...) if you need to change the resulting value. – jsgoupil Dec 06 '15 at 17:12