2

I have two classes:

public class Element
{
    public Item Item { get; set; }
}

public class Item
{
    public Element Element { get; set; }
}

And I have DTO with same structure for this classes. This method creates source data for mapping:

static Element[] CreateElements()
{
    var element2 = new Element();
    return new[]
    {
        new Element(new Item(element2)),
        element2,
        new Element()
    };
}

Then I configuring mapping and map elements:

Mapper.CreateMap<Element, ElementDto>();
Mapper.CreateMap<Item, ItemDto>();

var elements = CreateElements();

var mappedElements = elements
    .Select(_ => Mapper.Map(_, typeof(Element), typeof(ElementDto)))
    .OfType<ElementDto>()
    .ToArray();

After I check result of mapping:

foreach (var element in mappedElements)
{
    Console.WriteLine(mappedElements.Any(e => e?.Item?.Element == element));
}

This code shows "False" three times. It follows that the "element2" from "CreateElements" was created two copies.

The same test for the source elements will return "False True False":

foreach (var element in elements)
{
    Console.WriteLine(elements.Any(e => e?.Item?.Element == element));
}

As I need to configure the mapping so as not to duplicate elements? Is it possible?

Vlad
  • 1,377
  • 2
  • 17
  • 29

2 Answers2

1

I don't think it is AutoMapper issue.

You are creating three different Element items and map them to some kind of ElementDto. They are three different objects(both in terms of structure and reference), you cannot expect that if you map them to the same type, they will be equal.

If you consider your items:

    var element2 = new Element();
    return new[]
    {
        new Element(new Item(element2)),
        element2,
        new Element()
    };

and compare them, you will see that none is equal. You haven't provided ElementDto class bu my guess is that you should implement IEquatable interface, what will ensure proper comparison(or overload operators).

kamil-mrzyglod
  • 4,948
  • 1
  • 20
  • 29
  • Yes, I create three different elements. But after mapping I get four different elements. The fourth element is created when I copying `Item.Element`. – Vlad Dec 15 '15 at 08:08
  • @Vlad Do you use a custom equality comparer? Hard to say if this equality will even work; what if you serialize to Json or XML? – zaitsman Dec 15 '15 at 08:38
  • @Vlad Why do you say that you have four elements when you iterate over only three with your LINQ? – kamil-mrzyglod Dec 15 '15 at 08:41
  • @zaitsman, I don't use custom comparer. All my code shows in question. I can't use serializing to Json or XML. It is progect limitation. – Vlad Dec 15 '15 at 08:42
  • @Vlad you have to provide a IEquatable or comparer etc. Because right now try this: `var e1 = new Element(); var e2 = new Element(); var eq = e1 == e2;` The result is false. – zaitsman Dec 15 '15 at 08:46
  • @Kamo. I mean the number of different instances. `mappedElements` contains three different elements. One of them is filled with property `Item.Element`. This property links to fourth element. – Vlad Dec 15 '15 at 08:49
  • @Vlad Nevertheless you should try implementing `IEquatable` as I and @zaitsman suggest IMO. Those objects will never be considered equal. – kamil-mrzyglod Dec 15 '15 at 08:51
  • @zaitsman, of course it false. But I check this: `var e1 = new Element(); var e2 = new Element{ Item = new Item { Element = e1 } }; var eq = e1 == e2.Item.Element;` The result is true. And after mapping result must be true. – Vlad Dec 15 '15 at 09:00
  • @Vlad Not really; the only reason the above is true is because the reference to the object is the same; this is why i said try serializing and deserializing this. – zaitsman Dec 15 '15 at 09:13
  • @zaitsman I try to serialize/deserialize objects. The result was the same as when using automapper (false). I want to avtomapper not create copies of the objects. In this case the `element.Item.Element` and `element` would refer to one and the same object. Can automapper do that or not? – Vlad Dec 16 '15 at 08:03
  • @Vlad highly doubt that. – zaitsman Dec 20 '15 at 08:10
0

This can be done manually. First, ignore the property Item to AutoMapper did not copy chain of elements:

Mapper.CreateMap<Item, ItemDto>()
    .ForMember(_ => _.Element, _ => _.Ignore()); 

Secondly, copy the chain manually with a mark viewed items:

static IEnumerable<ElementDto> MapElements(Element[] elements)
{
    var elementToDtoMap = new Dictionary<Element, ElementDto>();

    foreach (var element in elements)
    {
        MapElement(element, null, elementToDtoMap);
    }

    return elementToDtoMap.Select(_ => _.Value);
}

static void MapElement(Element element, ItemDto parentItem, Dictionary<Element, ElementDto> elementToDtoMap)
{
    ElementDto elementDto = null;
    if (elementToDtoMap.TryGetValue(element, out elementDto))
            return;

    elementDto = Mapper.Map<ElementDto>(element);
    elementToDtoMap.Add(element, elementDto);

    if (parentItem != null)
    {
        parentItem.Element = elementDto;
    }

    if (element.Item != null)
    {
        MapElement(element.Item.Element, elementDto.Item, elementToDtoMap);
    }
}
Vlad
  • 1,377
  • 2
  • 17
  • 29