Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automapper does not map properly null List member, when the condition != null is specified

There is a problem when I try to map a null list (member) of an object, considering that I specified:

.ForAllMembers(opts => opts.Condition((src, dest, srcMember) =>
    srcMember != null
));
cfg.AllowNullCollections = true; // didn't help also

short example from code:

gi.PersonList = new List<Person>();
gi.PersonList.Add(new Person { Num = 1, Name = "John", Surname = "Scott" });
GeneralInfo gi2 = new GeneralInfo();
gi2.Qty = 3;

Mapper.Map<GeneralInfo, GeneralInfo>(gi2, gi);

gi.PersonList.Count = 0, how to fix that?

using System;
using System.Collections.Generic;
using AutoMapper;

public class Program
{
   public static void Main(string[] args)
    {
        Mapper.Initialize(cfg =>
        {
            cfg.AllowNullCollections = true;
            cfg.CreateMap<GeneralInfo, GeneralInfo>()
            .ForAllMembers(opts => opts.Condition((src, dest, srcMember) =>
                srcMember != null
            ));

        });
        GeneralInfo gi = new GeneralInfo();
        gi.Descr = "Test";
        gi.Dt = DateTime.Now;
        gi.Qty = 1;
        gi.PersonList = new List<Person>();
        gi.PersonList.Add(new Person { Num = 1, Name = "John", Surname = "Scott" });

        GeneralInfo gi2 = new GeneralInfo();
        gi2.Qty = 3;

        Console.WriteLine("Count antes de mapeo = " + gi.PersonList.Count);

        Mapper.Map<GeneralInfo, GeneralInfo>(gi2, gi);

        Console.WriteLine("Count despues de mapeo = " + gi.PersonList.Count);
        // Error : gi.PersonList.Count == 0 !!!! 
        //por que? si arriba esta: Condition((src, dest, srcMember) => srcMember != null ...

    }
}

class Person
{
    public int Num { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
}

class GeneralInfo
{
    public int? Qty { get; set; }
    public DateTime? Dt { get; set; }
    public string Descr { get; set; }
    public List<Person> PersonList { get; set; }
}

https://dotnetfiddle.net/N8fyJh

like image 518

People also ask

What is opt null substitute in automapper?

It "allows you to supply an alternate value for a destination member if the source value is null anywhere along the member chain" (taken from the AutoMapper manual ). cfg.CreateMap<Source, Destination> () .ForMember (dest => dest.Value, opt => opt.NullSubstitute (new IdNameDest { Id = src.Id }));

What is automapper in AutoCAD?

AutoMapper allows you to add conditions to properties that must be met before that property will be mapped. This can be used in situations like the following where we are trying to map from an int to an unsigned int.

How to create an automapper API using NuGet?

Click the "Create" button. After that, it will show the below screen where we’ve to select API template and click the "Create" button. The project structure will appear in the Solution Explorer like below. First, install Automapper related NuGet packages. For that, right-click on the project and choose the "Manage NuGet Packages" option.

How to get automapper mapexpression from typemap?

After you initialize AutoMapper, you can poke into the TypeMap to pull out its MapExpression - what does that look like? This is the lambda expression AutoMapper then compiles into the runtime mapping delegate. Sorry, something went wrong.


1 Answers

This should work but I'm not sure if you want to micro manage it like that:

cfg.AllowNullCollections = true;
cfg.CreateMap<GeneralInfo, GeneralInfo>()
    .ForMember(x => x.PersonList, opts => opts.PreCondition((src) => src.PersonList != null));

Problem is the collections that are handled specifically (that's true for most mappers though AutoMapper is a bit weird in this case, it's not my favorite) and seem to require the destination collection to be initialized. As I can see, collections are not copied in entirety which makes sense, but you need to initialize and copy individual items (this is my deduction but does sound right).

I.e. even if you skip the source, destination would still end up reinitialized (empty).

Problem as it seems is the Condition which is, given their documentation, applied at some later point, at which time the destination has already been initialized.

PreCondition on the other hand has a different signature to be used like you intended, as it doesn't take actual values, just source is available.

The only solution that seems to work is to use "per member" PreCondition (like the above).


EDIT:
...or this (using the ForAllMembers), but a bit ugly, reflection etc.

cfg.CreateMap<GeneralInfo, GeneralInfo>()
.ForAllMembers(opts =>
    {
        opts.PreCondition((src, context) =>
        {
            // we can do this as you have a mapping in between the same types and no special handling
            // (i.e. destination member is the same as the source property)
            var property = opts.DestinationMember as System.Reflection.PropertyInfo;
            if (property == null) throw new InvalidOperationException();
            var value = property.GetValue(src);
            return value != null;
        });
    }
);

...but there doesn't seem to be any cleaner support for this.


EDIT (BUG & FINAL THOUGHTS):

Conditional mapping to existing collection doesn't work from version 5.2.0 #1918

As pointed out in the comment (by @LucianBargaoanu), this seems to be a bug really, as it's inconsistent in this 'corner' case (though I wouldn't agree on that, it's a pretty typical scenario) when mapping collections and passing the destination. And it pretty much renders the Condition useless in this case as the destination is already initialized/cleared.

The only solution indeed seems to be the PreCondition (but it has issues given the different signature, I'm personally not sure why they don't pass the same plethora of parameters into the PreCondition as well?).

And some more info here:

the relevant code (I think)

nest collection is clear when using Condition but not Ignore #1940

Collection property on destination object is overwritten despite Condition() returning false #2111

Null source collection emptying destination collection #2031

like image 90
NSGaga-mostly-inactive Avatar answered Sep 28 '22 08:09

NSGaga-mostly-inactive