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
Will who also owns
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.
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.
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.
Alternatively, AutoMapper supports convention based mapping of enum values in a separate package AutoMapper.
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.
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]);
}
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");
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With