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
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
Try like this;
gi = Mapper.Map<GeneralInfo, GeneralInfo>(gi2);
I encounterted with this problem recently and somehow if the destination and sources are same type, the destination parameter doesn't work as expected.
Also, I want to notify that it isn't relevant with just Collection
objects. If you debug the gi
object you will see that the other properties are remaining with old values too. Somehow Automapper
doesn't change the property values which assigned before if you pass the destination instance as destionation parameter. I think, it is mostly relevant that Automapper
was not designed for creating copy/clone objects.