How to prevent values in "Target" object being overwritten by nulls from "Source" object when using ValueInjector or Automapper? Nested Mapping Issue?

StackOverflow https://stackoverflow.com/questions/13631942

سؤال

My Problem

"Source" object Properties of the same class that do not exist in the View, are overwritting the same properties in the "Target" object with nulls. How do I prevent this? In affect how do I ensure only populated(not null) properties are merged into the "Target" object. I have also tried this with Automapper and failed, but I would be happy with an Automapper solution as an alternative.

I do appreciate that this "Null Mapping" question has appeared before, but I fear my situation is more involved since there are nested objects. Well I tried the suggested options and I could not get them to work. So here I am.

Huge gratitude for any help.

I appreciate this is a complex problem, and really, really appreciate any help with this, particularly if someone can produce a code sample for me. I have pulling my hair out over this for a few days :(

What I have attempted

I have 2 objects, one is the original("Target"), one("Source") is populated by a form ie a View.

The Original "Target" Object(myOrigDoc) looks like this:

enter image description here

The Form "Source" Object(myDoc) looks like this:

enter image description here

I then do the mapping:

            myOrigDoc.Introduction.InjectFrom<StrNotNull>(myDoc.Introduction);

using the following injector:

    public class StrNotNull: ConventionInjection
{
    bool blnNotNull = false;
    bool blnMatch = false;
    protected override bool Match(ConventionInfo c)
    {
        blnNotNull = false;
        blnMatch = false;

        //if ((c.SourceProp.Type == typeof(string)) && (c.SourceProp.Value != null))
        //    blnAssignable = true;

        if (c.SourceProp.Value != null)
            blnNotNull = true;

        if ((c.SourceProp.Name == c.TargetProp.Name) && (blnNotNull)) 
            blnMatch = true;

        return blnMatch;
    }
}

and I end up with:

enter image description here

The Form has no "DateOfBirth" field on it, therefore I suspect Model Binding is creating a null value for the "DataOfBirth" property, on the new "MyDoc" object, when I call:

        public ActionResult Index(Document myDoc)

Many thanks, Ed.

EDIT1: I believe this is a nested mapping problem due to the subclasses. Not sure how I sort this in ValueInjector.

EDIT2: Possible Automapper Solution from documentation for nested mappings, but I could not get it to work. I still get my nulls going across into the target.:

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

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

Mapper.CreateMap<XSD_Smart2.Client, XSD_Smart2.Client>().ForAllMembers(opt => 
opt.Condition(srs => !srs.IsSourceValueNull));
هل كانت مفيدة؟

المحلول

Update for ValueInjecter 3

public class IgnoreNulls : LoopInjection
{
    protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
    {
        var val = sp.GetValue(source);
        if (val != null)
        {
            tp.SetValue(target, val);
        }
    }
}

previous version

create a custom injection that will have this behaviour:

    public class IgnoreNulls : ConventionInjection
    {
        protected override bool Match(ConventionInfo c)
        {
            return c.SourceProp.Name == c.TargetProp.Name
                  && c.SourceProp.Value != null;
        }
    }

and use it:

    target.InjectFrom<IgnoreNulls>(source);

نصائح أخرى

This simple AutoMapper test works for me:

Classes

public class Client
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

AutoMapperConfiguration

public class MyProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<Client, Client>()
            .ForAllMembers(opt => opt.Condition(src => !src.IsSourceValueNull));
    }
}

Unit Tests

[TestFixture]
public class MappingTests
{
    [Test]
    public void AutoMapper_Configuration_IsValid()
    {
        Mapper.Initialize(m => m.AddProfile<MyProfile>());
        Mapper.AssertConfigurationIsValid();
    }

    [Test]
    public void AutoMapper_ClientMapping_IsValid()
    {
        Mapper.Initialize(m => m.AddProfile<MyProfile>());
        Mapper.AssertConfigurationIsValid();

        var source = new Client
            {
                FirstName = "SourceFirstName1",
                LastName = null
            };

        var destination = new Client
            {
                FirstName = "DestinationFirstName1",
                LastName = "DestinationLastName1"
            };

        destination = Mapper.Map(source, destination);

        Assert.That(destination, Is.Not.Null);
        Assert.That(destination.FirstName, Is.EqualTo("SourceFirstName1"));
        Assert.That(destination.LastName, Is.EqualTo("DestinationLastName1"));
    }
}

UPDATE

Interestingly, when you use this mapping to map a list, it fails. IE - this test fails:

[Test]
public void AutoMapper_ClientListMapping_IsValid()
{
    Mapper.Initialize(m => m.AddProfile<MyProfile>());
    Mapper.AssertConfigurationIsValid();

    var source = new List<Client>
        {
            new Client
                {
                    FirstName = "SourceFirstName1",
                    LastName = null
                },
            new Client
                {
                    FirstName = null,
                    LastName = "SourceLastName2"
                }
        };

    var destination = new List<Client>
        {
            new Client
                {
                    FirstName = "DestinationFirstName1",
                    LastName = "DestinationLastName1"
                },
            new Client
                {
                    FirstName = "DestinationFirstName2",
                    LastName = "DestinationLastName2"
                }
        };

    destination = Mapper.Map(source, destination);

    Assert.That(destination, Is.Not.Null);
    Assert.That(destination.Count, Is.EqualTo(2));
    Assert.That(destination[0].FirstName, Is.EqualTo("SourceFirstName1"));
    Assert.That(destination[0].LastName, Is.EqualTo("DestinationLastName1"));
    //  /\  Line above went BANG!  /\
    Assert.That(destination[1].FirstName, Is.EqualTo("DestinationFirstName2"));
    Assert.That(destination[1].LastName, Is.EqualTo("SourceLastName2"));
}

This looks like a bug in AutoMapper (in 2.2.0 and 2.2.1-ci9000)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top