Страницы

Поиск по вопросам

понедельник, 16 декабря 2019 г.

Настройка маппинга элементов

#c_sharp #automapper


Есть два класса, для которые нужно сначала сохранить в некоторых DTO-объектах, которые
полностью повторяют структуру этих классов, а затем снова развернуть в исходное состояние:

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

public class ParentItem
{
    public Element Parent { get; set; }
}


Метод, который создает исходные данные:

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


Конфигурация автомаппера и маппинг созданных элементов:

Mapper.CreateMap();
Mapper.CreateMap();
Mapper.CreateMap();
Mapper.CreateMap();

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


Далее проверяем, сохранилась ли структура элементов после маппинга:

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


Этот код трижды выводит False. Из чего следует, что для объекта element2 из метода
CreateElements было сделано две копии. Первая при копировании перечня элементов, вторая
при копировании ParentItem.Parent.

Как нужно настроить маппинг, чтобы в ParentItem.Parent не создавалась еще одна копия
элемента?
    


Ответы

Ответ 1



Как выяснилось, AutoMapper подобные штуки не умеет разруливать. Т.е. копии объектов все равно будут создаваться. Так что подобные зависимости придется разруливать руками. В данном случае можно сделать примерно следующее. Во-первых, при маппинге не копировать всю цепочку элементов, а скопировать только Element и его Item. Остальная часть цепочки копируется с предварительными проверками на дублирование элементов. Mapper.CreateMap() .ForMember(_ => _.Element, _ => _.Ignore()); Во-вторых, нужно запоминать элементы, которые уже были скопированы и заполнять Item вручную. static IEnumerable MapElements(Element[] elements) { var elementToDtoMap = new Dictionary(); foreach (var element in elements) { MapElement(element, null, elementToDtoMap); } return elementToDtoMap.Select(_ => _.Value); } static void MapElement(Element element, ItemDto parentItem, Dictionary elementToDtoMap) { ElementDto elementDto = null; if (elementToDtoMap.TryGetValue(element, out elementDto)) return; elementDto = Mapper.Map(element); elementToDtoMap.Add(element, elementDto); if (parentItem != null) { parentItem.Element = elementDto; } if (element.Item != null) { MapElement(element.Item.Element, elementDto.Item, elementToDtoMap); } } Таким образом, можно добиться желаемого результата. Код, конечно, будет сильно зависить от конкретного случая. Но общий принцип примерно такой.

Ответ 2



Есть два класса, для которые нужно сначала сохранить в некоторых DTO-объектах, которые полностью повторяют структуру этих классов, а затем снова развернуть в исходное состояние Если DTO используются в качестве неизменяемого типа (т.е. его поля/свойства - readonly), то в такой ситуации вместо копирования данных проще использовать интерфейc и передавать его между модулями. public interface ElementDto { int Value { get; } } class Module1 { static public void Process(ElementDto dto) { // тут можно только получить значения из dto } } class Module2 { class Element : ElementDto { public int Value { get; set; } } static public void Run() { var e = new Element() { Value = 123 }; Module1.Process(e); // передаем ElementDto -- его Value - readonly } } В этом решении есть один "минус". В методе Module1.Process, несмотря на то, что Element скрыт за интерфейсом ElementDto, при помощи рефлексии можно изменить значение Element.Value, так: dto.GetType().GetMethod("set_Value").Invoke(e, new object [] { 321 });

Комментариев нет:

Отправить комментарий