AutoMapper.Collection - Skip mapping entirely if source collection property is null

Viewed 542

Is there any way to get AutoMapper.Collection to skip the mapping of a collection if the source collection property is null?

In my case, the client may want to signal to the API that there is no need to update a given collection. A logical way do this would be to set the collection property to null in the dto sent from the client.

Basically:

  • If the source collection property is null: Do not touch the destination collection at all but leave it as-is. Do not clear it
  • If the source collection is not null: Do the collection mapping. If the source collection is empty this means clearing the destination collection

Is there any way to achieve this in AutoMapper.Collection? What I am looking for is this:

// Sample Classes
public class Entity
{
    public ICollection<EntityChild> Children { get; set; }
}

public class EntityChild
{
    public int Id { get; set; }
    public string Value { get; set; }
}

public class Dto
{
    public ICollection<DtoChild> Children { get; set; }
}

public class DtoChild
{
    public int Id { get; set; }
    public string Value { get; set; }
}

// AutoMapper setup including equality for children
CreateMap<Dto, Entity>();
CreateMap<DtoChild, EntityChild>()
    .EqualityComparison((src, dst) => src.Id == dst.Id)
    .ReverseMap();

// Sample 1, null source collection
var entity = new Entity
{
    Children = new List<EntityChild>
    {
        new() { Id = 1, Value = "Value 1" },
        new() { Id = 2, Value = "Value 2" }
    }
};

var dtoSkipChildren = new Dto
{
    Children = null
};

// Since the source Children property is null, do not update the destination collection
mapper.Map(dtoSkipChildren, entity);

// Sample 2, empty source collection
entity = new Entity
{
    Children = new List<EntityChild>
    {
        new() { Id = 1, Value = "Value 1" },
        new() { Id = 2, Value = "Value 2" }
    }
};

var dtoClearChildren = new Dto
{
    Children = new List<DtoChild>()
};

// Now the source children is not null (but empty) so the destination collection should 
// be updated (in this case cleared since the source collection is empty)
mapper.Map(dtoClearChildren, entity);

AutoMapper.Collection treats the null source Children property the same as the source Children property containing an empty collection. In both cases the destination Children collection is cleared.

I have tried to tell AutoMapper to skip the source.Children property if null:

CreateMap<Dto, Entity>()
    .ForMember(dst => dst.Children, opt => opt.Condition(src => null != src.Children));

This does not change things. Also in this case, the source collection property is null when AutoMapper.Collection sets to work and the destination collection is cleared.

This is not a real solution either:

CreateMap<Dto, Entity>()
    .ForMember(
        src => src.Children, 
        opt => opt.MapFrom((src, dst, _, ctx) => src.Children ?? ctx.Mapper.Map<ICollection<DtoChild>>(dst.Children)));

This means reverse mapping the destination collection to the (null) source collection, so it can be mapped back. An ugly hack:

  • It's crossing the river twice to end up where you started
  • Wasted effort which is silly on large collections
  • Risky as the reverse map for other reasons may not be 100% thus introducing pretty hidden bugs

Does anyone have an advice on how to achieve this - or why my use case is not a good idea?

1 Answers

I had the same problem and I figured it out this way

CreateMap<Dto, Entity>()
    .ForMember(
        dest=> dest.Children, 
        opt => {
                    opt.PreCondition(src =>src.Children!= null);
                    opt.MapFrom(src =>src.Children);

                });

Then in the place, you want to use mapping write code like this:

entity = mapper.Map<Dto, Entity>(dto , entity);

opt.PreCondition(src =>src.Children!= null) basically says to AutoMapper to proceed to map if the source field is not empty else do not map.

to read more look at this https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions

Related