Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper, how to keep references between mapped objects?

I am using AutoMapper to convert a UI model to POCOs that I later serialize to XML using a DataContractSerializer in order to preserve the references between them.

The problem comes that, when mapping, the references between those entities are lost.

The UI classes reference each other, but the mapping process makes new instances for every reference, so the original relations are broken :(

Let me explain:

I have 2 entities of type Person

    Person 
    { 
        List<House> OwnedHouses 
    }

And these 2 objects

John who owns

  • House1

Will who also owns

  • House1

When AutoMapper maps each Person correctly, but when it also maps House1 as two different instances!!

So I have a two copies of House1. John owns his House1 (#1) and Will owns his House1 (#2).

They are not linked anymore.

Is there any way to keep the relations that originally existed?

Thanks.

EDITED: Actually what I have is this:

A Document contains a list of ChildDocuments. Each ChildDocument has a list of Designables (Rectangles, Lines, Ellipses…) and a especial designable called ChildDocumentAdapter that contains itself ANOOTHER ChildDocument. This is the trouble, it can reference another ChildDocument.

The diagram

like image 740
SuperJMN Avatar asked May 21 '13 09:05

SuperJMN


People also ask

When should you not use AutoMapper?

If you have to do complex mapping behavior, it might be better to avoid using AutoMapper for that scenario. Reverse mapping can get very complicated very quickly, and unless it's very simple, you can have business logic showing up in mapping configuration.

What can I use instead of AutoMapper?

AutoMapper is one of the popular object-object mapping libraries with over 296 million NuGet package downloads. It was first published in 2011 and its usage is growing ever since. Mapster is an emerging alternative to AutoMapper which was first published in 2015 and has over 7.4 million NuGet package downloads.

Can AutoMapper map enums?

Alternatively, AutoMapper supports convention based mapping of enum values in a separate package AutoMapper.

Does AutoMapper map private fields?

By default, AutoMapper only recognizes public members. It can map to private setters, but will skip internal/private methods and properties if the entire property is private/internal.


2 Answers

If I'm understanding the question, you're performing two separate mapping operations - one for John, another for Will.

@Sunny is right. AutoMapper is not designed to do this. Each call you make to Mapper.Map() is typically independent of any other. By using the same instance of the HouseListConverter, you get the benefit of caching all mapped houses in a dictionary. But you have to either register it globally or pass it as an option to the mapping calls you want grouped together. That's not just extra work, it's hiding a very important implementation detail deep within the converter.

If you map both John and Will in one operation, by putting them into a collection, the output would be what you want without the need for a custom converter or resolver.

It may be an easier alternative for other people with a similar problem.

public void MapListOfPeopleWithSameHouse()
{
    Mapper.CreateMap<Person, PersonDTO>();
    Mapper.CreateMap<House, HouseDTO>();

    var people = new List<Person>();
    var house = new House() { Address = "123 Main" };
    people.Add(new Person() { Name = "John", Houses = new List<House>() { house } });
    people.Add(new Person() { Name = "Will", Houses = new List<House>() { house } });

    var peopleDTO = Mapper.Map<List<PersonDTO>>(people);
    Assert.IsNotNull(peopleDTO[0].Houses);
    Assert.AreSame(peopleDTO[0].Houses[0], peopleDTO[1].Houses[0]);
}
like image 56
drew Avatar answered Oct 19 '22 02:10

drew


While Automapper is not designed with this in mind, it's powerful enough to let you do it, using custom type converters. You need to create your own converter from IList<House> to IList<HouseDto>, and inject it using a factory:

using System;
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
using SharpTestsEx;

namespace StackOverflowExample
{
    public class House
    {
        public string Address { get; set; }
    }

    public class Person
    {
        public IList<House> OwnedHouse { get; set; }
    }

    public class HouseDto
    {
        public string Address { get; set; }
    }

    public class PersonDto
    {
        public IList<HouseDto> OwnedHouse { get; set; }
    }

    [TestFixture]
    public class AutomapperTest
    {
        public interface IHouseListConverter : ITypeConverter<IList<House>, IList<HouseDto>>
        {
        }

        public class HouseListConverter : IHouseListConverter
        {
            private readonly IDictionary<House, HouseDto> existingMappings;

            public HouseListConverter(IDictionary<House, HouseDto> existingMappings)
            {
                this.existingMappings = existingMappings;
            }

            public IList<HouseDto> Convert(ResolutionContext context)
            {
                var houses = context.SourceValue as IList<House>;
                if (houses == null)
                {
                    return null;
                }

                var dtos = new List<HouseDto>();
                foreach (var house in houses)
                {
                    HouseDto mapped = null;
                    if (existingMappings.ContainsKey(house))
                    {
                        mapped = existingMappings[house];
                    }
                    else
                    {
                        mapped = Mapper.Map<HouseDto>(house);
                        existingMappings[house] = mapped;
                    }
                    dtos.Add(mapped);
                }

                return dtos;
            }
        }

        public class ConverterFactory
        {
            private readonly IHouseListConverter resolver;
            public ConverterFactory()
            {
                resolver = new HouseListConverter(new Dictionary<House, HouseDto>());
            }

            public object Resolve(Type t)
            {
                return t == typeof(IHouseListConverter) ? resolver : null;
            }
        }

        [Test]
        public void CustomResolverTest()
        {
            Mapper.CreateMap<House, HouseDto>();
            Mapper.CreateMap<IList<House>, IList<HouseDto>>().ConvertUsing<IHouseListConverter>();
            Mapper.CreateMap<Person, PersonDto>();

            var house = new House {Address = "any"};
            var john = new Person {OwnedHouse = new List<House> {house}};
            var will = new Person { OwnedHouse = new List<House> { house } };

            var converterFactory = new ConverterFactory();
            var johnDto = Mapper.Map<PersonDto>(john, o=>o.ConstructServicesUsing(converterFactory.Resolve));
            var willDto = Mapper.Map<PersonDto>(will, o=>o.ConstructServicesUsing(converterFactory.Resolve));

            johnDto.OwnedHouse[0].Should().Be.SameInstanceAs(willDto.OwnedHouse[0]);
            johnDto.OwnedHouse[0].Address.Should().Be("any");
        }
    }
}  
like image 44
Sunny Milenov Avatar answered Oct 19 '22 02:10

Sunny Milenov