Используя Automapper, преобразование DTO обратно в Entity Framework, включая ссылочные объекты

У меня есть доменные сущности POCO, которые сохраняются с помощью Entity Framework 5. Они получены из DbContext с использованием шаблона репозитория и предоставляются приложению RESTful MVC WebApi через шаблон UoW. Объекты POCO являются прокси и лениво загружены.

Я конвертирую свои сущности в DTO перед тем, как отправить их клиенту. Я использую Automapper для этого, и, похоже, он работает нормально, когда Automapper отображает POCO прокси в DTO, сохраняя свойства навигации без изменений. Я использую следующее сопоставление для этого:

    Mapper.CreateMap<Client, ClientDto>();

Пример объекта Domain/DTO:

[Serializable]
public class Client : IEntity
{
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public virtual string Name { get; set; }

    public virtual ICollection<ClientLocation> ClientLocations { get; set; }

    public virtual ICollection<ComplianceRequirement> DefaultComplianceRequirements { get; set; }

    public virtual ICollection<Note> Notes { get; set; }
}

public class ClientDto : DtoBase
{
    public int Id { get; set; }

    [Required, MaxLength(100)]
    public string Name { get; set; }

    public ICollection<ClientLocation> ClientLocations { get; set; }

    public ICollection<ComplianceRequirementDto> DefaultComplianceRequirements { get; set; }

    public ICollection<Note> Notes { get; set; }
}

Теперь я пытаюсь обновить свой контекст, используя DTO, отправленные обратно с провода. У меня возникают конкретные проблемы с настройкой навигационных свойств / связанных объектов. Сопоставление для этого я использую:

    Mapper.CreateMap<ClientDto, Client>()
        .ConstructUsing((Func<ClientDto, Client>)(c => clientUow.Get(c.Id)));

Выше clientUow.Get() ссылается на DbContext.Set.Find(), так что я получаю отслеживаемый прокси-объект POCO от EF (который содержит все связанные сущности также в качестве прокси).

В моем методе контроллера я делаю следующее:

    var client = Mapper.Map<ClientDto, Client>(clientDto);
    uow.Update(client);

Клиент успешно сопоставлен как прокси-объект POCO, однако его связанные сущности / навигационные свойства заменены новым (непрокси) сущностью POCO со значениями свойств, скопированными из DTO.

Выше uow.Update() в основном относится к функции, которая выполняет постоянную логику, которая у меня есть как:

    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;
    _context.SaveChanges();

Вышеупомянутое не сохраняется, даже сохраняется сущность, не говоря уже о связанных. Я пробовал варианты отображений и различные способы сохранения, используя отсоединение / состояния, но всегда получаю исключения "объект с таким же ключом уже существует в ObjectStateManager".

Я посмотрел на бесчисленное множество других потоков и просто не могу заставить все это работать с Automapper. Я могу получить прокси-объект из контекста и вручную пройти через свойства, обновляя их из DTO, но я использую Automapper для сопоставления домена -> DTO, и было бы более элегантно использовать его для обратного, так как мои DTO похожи на мои доменные объекты в значительной степени.

Существует ли учебный способ работы с Automapper с помощью EF с помощью доменных объектов / DTO, имеющих навигационные свойства, которые также необходимо обновлять одновременно?

ОБНОВИТЬ:

    var originalEntity = _entities.Find(entity.Id);
    _context.Entry<T>(originalEntity).State = System.Data.EntityState.Detached;
    _context.Entry<T>(entity).State = System.Data.EntityState.Modified;

Приведенная выше логика постоянства обновляет "корневой" прокси-объект EF в контексте, однако любые связанные объекты не обновляются. Я предполагаю, что это связано с тем, что они не сопоставляются с прокси-объектами EF, а представляют собой простые доменные объекты. Помощь будет наиболее ценится!

ОБНОВЛЕНИЕ: Кажется, что то, чего я пытаюсь достичь, на самом деле невозможно, используя текущую версию EF(5), и что это основное ограничение EF, а не Automapper:

Ссылка на сайт

Ссылка на сайт

Я думаю, что это вернулось к тому, чтобы сделать это вручную. Надеюсь, что это помогает кому-то еще, кому интересно то же самое.

3 ответа

То, что вы хотите сделать, это сначала получить сущность из базы данных:

var centity = _context.Client.First(a=>a.Id = id)

Затем вы отображаете это и обновляете (это то, что вы искали, он будет отображать только то, что находит в inputDTO, и оставит другие свойства в покое)

Mapper.Map<UpdateClientInput,  Client>(inputDto, centity);
_context.update();

Вы уже определили проблему:

Приведенная выше логика сохранения обновляет "корневой" прокси-объект EF в контексте, однако все связанные объекты не обновляются

Вы устанавливаете измененное состояние только для корневого узла. Вы должны написать код для перебора всех объектов и установить состояние в измененное.

Я реализовал шаблон для обработки этого состояния модели иерархии с EF.

Каждый класс модели сущности реализует интерфейс, как показано ниже, как и классы модели представления:

public interface IObjectWithState
{
    ObjectState ObjectState { get; set; }
}

Перечисление ObjectState определено ниже:

public enum ObjectState
{
    Unchanged  = 0,
    Added = 1,
    Modified = 2,
    Deleted = 3
}

Например, при сохранении глубокой иерархии объектов с использованием EF я сопоставляю объекты модели представления с их эквивалентными объектами, включая ObjectState.

Затем я присоединяю объект корневого объекта к контексту (и, следовательно, ко всем дочерним объектам):

dbContext.MyCustomEntities.Attach(rootEntityObj);

Затем у меня есть метод расширения для DbContext, который перебирает все элементы в трекере изменений контекста и обновляет состояние каждой сущности (как вы сделали выше).

    public static int ApplyStateChanges(this DbContext context)
    {
        int count = 0;
        foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
        {
            IObjectWithState stateInfo = entry.Entity;
            entry.State = ConvertState(stateInfo.ObjectState);
            if (stateInfo.ObjectState != ObjectState.Unchanged)
                count++;
        }
        return count;
    }

Тогда мы можем просто сохранить изменения как обычно:

dbContext.SaveChanges();

Таким образом, вся иерархия дочерних объектов будет соответствующим образом обновлена ​​в базе данных.

Другие вопросы по тегам