Alexey
Alexey

Reputation: 11

Automapper not mapped inside nested collection is not working properly

All. For example we have such simple classes

public class SimpleModel
{
    public int PropertyId { get; set; }

    public ICollection<SimpleModelCollectionItem> SimpleCollection { get; set; }
}

public class SimpleModelCollectionItem
{
    public int PropertyId { get; set; }
}

public class SimpleEntity
{
    public int Id { get; set; }
    public int PropertyId { get; set; }

    public virtual ICollection<SimpleEntityCollectionItem> SimpleCollection { get; set; }
}

public class SimpleEntityCollectionItem
{
    public int Id { get; set; }
    public int PropertyId { get; set; }
}

and we have some configuration code

AutoMapper.Mapper.CreateMap<SimpleModel, SimpleEntity>()
            .ForMember(dest => dest.Id, src => src.Ignore())
            .ForMember(dest => dest.SimpleCollection, src => src.UseDestinationValue());

        AutoMapper.Mapper.CreateMap<SimpleModelCollectionItem, SimpleEntityCollectionItem>()
            .ForMember(dest => dest.Id, src => src.Ignore());

        AutoMapper.Mapper.AssertConfigurationIsValid();

and test data initialization

            var model = new SimpleModel
                        {
                            PropertyId = 2,
                            SimpleCollection =
                                new List<SimpleModelCollectionItem>
                                    {
                                        new SimpleModelCollectionItem
                                            {
                                                PropertyId = 7
                                            }
                                    }
                        };
        var entity = new SimpleEntity
                         {
                             Id = 1,
                             PropertyId = 3,
                             SimpleCollection =
                                 new List<SimpleEntityCollectionItem>
                                     {
                                         new SimpleEntityCollectionItem
                                             {
                                                 Id = 5,
                                                 PropertyId = 4
                                             }
                                     }
                         };
        AutoMapper.Mapper.Map(model, entity);

and I expect to see

Console.WriteLine(entity.Id); // 1
Console.WriteLine(entity.PropertyId); // 2
Console.WriteLine(entity.SimpleCollection.First().Id); // 5 but was 0
Console.WriteLine(entity.SimpleCollection.First().PropertyId); // 7

Is it possible to set Id for inner collection equals to 5 as it was initially?

Upvotes: 1

Views: 1599

Answers (1)

Yacoub Massad
Yacoub Massad

Reputation: 27861

So, when AutoMapper was mapping your collection, it actually removed the old item from the destination collection and then added a new item.

You can verify this by using this code:

var item_before = entity.SimpleCollection.First();

AutoMapper.Mapper.Map(model, entity);

var item_after = entity.SimpleCollection.First();

bool item_same_references = object.ReferenceEquals(item_before, item_after);

The value of item_same_references will be false.

This happens even if you are using src => src.UseDestinationValue(). Using this only means that the collection object it self should be reused, but not the items in that collection.

I am not sure if there is a way to tell AutoMapper to also use the same collection items.

Also, thinking about it, what happens if the destination collection contains more of fewer items than the source collection?

So, the zero you are getting is because when AutoMapper creates the new item to add to the collection, the default value of the Id property is default(int) which is zero.

I suggest the following:

I am assuming that the number of items in the source and destination collections are equal.

First, modify your mapping configuration to instruct AutoMapper to ignore the collection like this:

AutoMapper.Mapper.CreateMap<SimpleModel, SimpleEntity>()
    .ForMember(dest => dest.Id, opt => opt.Ignore())
    .ForMember(dest => dest.SimpleCollection, opt => opt.Ignore());

Then, create a method like this that maps collection items in place (without removing and then adding new items) like this:

public void MapCollectionsInPlace<TSource, TDestination>(IEnumerable<TSource> source_collection,
    IEnumerable<TDestination> destination_collection)
{
    var source_enumerator = source_collection.GetEnumerator();
    var destination_enumerator = destination_collection.GetEnumerator();

    while (source_enumerator.MoveNext())
    {
        if(!destination_enumerator.MoveNext())
            throw new Exception("Source collection has more items than destination collection");

        AutoMapper.Mapper.Map(source_enumerator.Current, destination_enumerator.Current);
    }
}

Now, you can use the following code:

AutoMapper.Mapper.Map(model, entity);

MapCollectionsInPlace(model.SimpleCollection, entity.SimpleCollection);

Upvotes: 1

Related Questions