Используйте AutoMapper для добавления, обновления и удаления элементов в списке - PullRequest
0 голосов
/ 16 января 2019

Нет ли в AutoMapper собственного подхода к обновлению вложенных списков, в которых необходимо удалить экземпляры, добавить или обновить ?

Здравствуйте, я использую AutoMapper в своем приложении ASP.Net Core с EF Core для сопоставления ресурсов API с моими моделями. Это работало нормально для большей части моего приложения, но я не доволен своим решением для обновления отображенных вложенных списков, где перечисленные экземпляры должны сохраняться. Я не хочу перезаписывать существующий список, я хочу удалить экземпляры, которых больше нет в моем входящем ресурсе, добавить новые экземпляры и обновить существующие экземпляры.

Модели:

public class MainObject
{
    public int Id { get; set; }
    public List<SubItem> SubItems { get; set; }

}

public class SubItem
{
    public int Id { get; set; }
    public int MainObjectId { get; set; }
    public MainObject MainObject { get; set; }
    public double Value { get; set; }
}

Ресурсы:

public class MainObjectResource
{
    public int Id { get; set; }
    public ICollection<SubItemResource> SubItems { get; set; }

}

public class SubItemResource
{
    public int Id { get; set; }
    public double Value { get; set; }
}

Контроллер:

public class MainController : Controller
{
    private readonly IMapper mapper;
    private readonly IMainRepository mainRepository;
    private readonly IUnitOfWork unitOfWork;

    public MainController(IMapper mapper, IMainRepository mainRepository, IUnitOfWork unitOfWork)
    {
        this.mapper = mapper;
        this.mainRepository = mainRepository;
        this.unitOfWork = unitOfWork;
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateMain(int id, [FromBody] MainObjectResource mainObjectResource)
    {
        MainObject mainObject = await mainRepository.GetMain(id);
        mapper.Map<MainObjectResource, MainObject>(mainObjectResource, mainObjectResource);
        await unitOfWork.CompleteAsync();

        return Ok(); 
    }
}

Профиль отображения:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<MainObject, MainObjectResource>();
        CreateMap<SubItem, SubItemResource>();

        CreateMap<MainObject, MainObjectResource>().ReverseMap()
        .ForMember(m => m.Id, opt => opt.Ignore())
        .ForMember(m => m.SubItems, opt => opt.Ignore())
        .AfterMap((mr, m) => {
                //To remove
                List<MainObject> removedSubItems = m.SubItems.Where(si => !mr.SubItems.Any(sir => si.Id == sir.Id)).ToList();
                foreach (SubItem si in removedSubItems)
                    m.SubItems.Remove(si);
                //To add
                List<SubItemResource> addedSubItems = mr.SubItems.Where(sir => !m.SubItems.Any(si => si.Id == sir.Id)).ToList();
                foreach (SubItemResource sir in addedSubItems)
                    m.SubItems.Add( new SubItem {
                        Value = sir.Value,
                    });
                // To update
                List<SubItemResource> updatedSubItems = mr.SubItems.Where(sir => m.SubItems.Any(si => si.Id == sir.Id)).ToList();
                SubItem subItem = new SubItem();
                foreach (SubItemResource sir in updatedSubItems)
                {
                    subItem = m.SubItems.SingleOrDefault(si => si.Id == sir.Id);
                    subItem.Value = sir.Value;
                }
            });
    }
}

То, что я здесь делаю, - это настраиваемое отображение, но я чувствую, что это настолько общий случай, что я ожидаю, что AutoMapper сможет справиться с этим с помощью некоторого расширения. Я видел несколько примеров, когда профиль сопоставления использует настраиваемое сопоставление (.AfterMap), но затем фактическое сопоставление выполняется статическим экземпляром AutoMapper. Я не уверен, подходит ли это для моего использования AutoMapper через внедрение зависимостей: я не опытный программист, но мне это не кажется правильным.

Ответы [ 2 ]

0 голосов
/ 17 января 2019

Благодаря Ивану Стоеву я начал смотреть на AutoMapper.Collection , который действительно является расширением, которое я надеялся найти. После реализации мои списки обновляются так, как я хотел. Конфигурация проста в моем использовании, поскольку мне нужно только указать Id моих объектов.

Моя начальная конфигурация изменена на:

using AutoMapper;
using AutoMapper.EquivalencyExpression;
[....]
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAutoMapper(cfg => {
                cfg.AddCollectionMappers();
                });
        }
[....]

И мой профиль отображения:

    CreateMap<MainObject, MainObjectResource>().ReverseMap()
        .ForMember(m => m.Id, opt => opt.Ignore());

    CreateMap<SubItem, SubItemResource>().ReverseMap()
        .EqualityComparison((sir, si) => sir.Id == si.Id);
0 голосов
/ 16 января 2019

Эта проблема сложнее решить, чем кажется на первый взгляд. Вам достаточно легко составить собственное отображение своих списков, потому что вы знаете свое приложение; AutoMapper нет. Например, что делает исходный элемент равным целевому элементу, так что AutoMapper сможет различить, что он должен отображать поверх существующего, а не добавлять? ПК? Какое свойство является ПК? Является ли это свойство одинаковым как для источника, так и для пункта назначения? На эти вопросы вы можете легко ответить в своем AfterMap, а не в AutoMapper.

В результате AutoMapper всегда отображает коллекции как новые элементы. Если это не то поведение, которое вам нужно, то тут приходят вещи типа AfterMap. Также имейте в виду, что AutoMapper не предназначен специально для работы с EF, что на самом деле является проблемой, а не AutoMapper. Отслеживание изменений EF является причиной проблем с отображением коллекции AutoMapper по умолчанию. В других ситуациях и сценариях это не проблема.

...