andycwk
andycwk

Reputation: 846

AutoMapper Map properties of nested objects if Not Null

I have the following sample objects..

  public class ComplexObject
  {
    public string Name { get; set; }
    public SimpleObject Child1 { get; set; }
    public SimpleObject Child2 { get; set; }
  }
  public class SimpleObject : IEquatable< SimpleObject >
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Gender { get; set; }
    public int? Age { get; set; }
  }

with the following AutoMapper configuration

  Mapper.CreateMap<SimpleObject, SimpleObject>()
    .ForAllMembers(expression=>expression.Condition(r=>!r.IsSourceValueNull));

  Mapper.CreateMap<ComplexObject, ComplexObject>()
    .ForAllMembers(expression=>expression.Condition(resolutionContext=>!resolutionContext.IsSourceValueNull));

and the following NUnit test...

[SetUp] public void Should_run_before_each_test()
{
  child1 = new SimpleObject { FirstName = "Tom", LastName = "Smith", Age = 34, Gender = "Male" };
  child2 = new SimpleObject { FirstName = "Andy", LastName = "Smith-bob", Age = 21, Gender = "Male" };
}

[ Test ]
public void Should_ignore_null_properties_in_nested_objects()
{
  var source = new ComplexObject()
  {
    Name = "blue",
    Child1 = new SimpleObject{FirstName = "dot", LastName = "net"}
  };
  var destination = new ComplexObject()
  {
    Name = "Andy",
    Child1 = child1,
    Child2 = child2
  };

  destination = Mapper.Map(source, destination);

  destination.Name.Should(Be.EqualTo(source.Name));
  destination.Child1.FirstName.Should(Be.EqualTo("dot"));
  destination.Child1.LastName.Should(Be.EqualTo("net")  );
  destination.Child1.Age.Should(Be.EqualTo(child1.Age)  );
  destination.Child1.Gender.Should(Be.EqualTo(child1.Gender)  );
}

The above test fails when asserting the age as AutoMapper is pushing null through to the destination object.

Am I expecting too much from AutoMapper, or have I missed some vital map configuration step.

The ultimate goal is to have a very complex domain object bound to incoming form data via an MVC action. AutoMapper will then be used to merge only non-null properties (at all depths of the object graph) into the real instance being maintained throughout a multi step form.

Just in case anyone needs to know... I have also tried the following mapping configuration without any luck :(

  Mapper.CreateMap<ComplexObject, ComplexObject>()
    .ForMember(x=>x.Child1, l=>l.ResolveUsing(x=>x.Child1 == null?null:Mapper.Map<SimpleObject,SimpleObject>(x.Child1)))
    .ForMember(x=>x.Child2, l=>l.ResolveUsing(x=>x.Child2 == null?null:Mapper.Map<SimpleObject,SimpleObject>(x.Child2)))
    .ForAllMembers(expression=>expression.Condition(resolutionContext=>!resolutionContext.IsSourceValueNull));

Upvotes: 3

Views: 4815

Answers (1)

scottm
scottm

Reputation: 28701

Why do you expect the value on the destination object is not null if the source value is null? The default value for int? is null, and therefore, when you call

destination.Child1.Age.Should(Be.EqualTo(child1.Age));

The .Should is what's failing. This should be expected behavior.

Try something like Assert.Null(detination.Child1.Age), and it should pass. AutoMapper is not 'pushing' the value through, there is no source value and therefore, Age is just going to have it's default value.

Upvotes: 1

Related Questions