Использование 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:
Ссылка на сайт
Ссылка на сайт
Я полагаю, это снова сделано вручную. Надеюсь, что это помогает кому-то еще, кому интересно то же самое.
Ответы
Ответ 1
Вы уже определили проблему:
Вышеупомянутая логика сохранения обновляет "прокси-сервер" корневого EF в контекст, однако любые связанные объекты не обновляются
Вы устанавливаете измененное состояние только в корневом каталоге node. Вы должны написать код для повторения всех объектов и изменения состояния.
Ответ 2
Я реализовал шаблон для обработки этого состояния модели иерархии с помощью 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();
Таким образом, вся иерархия дочерних объектов будет соответствующим образом обновлена в базе данных.
Ответ 3
Что вы хотите сделать, сначала получить Entity из базы данных:
var centity = _context.Client.First(a=>a.Id = id)
Затем вы сопоставляете это и обновляете (это то, что вы искали, оно будет отображать только то, что он находит в inputDTO, и оставить остальные свойства в покое)
Mapper.Map<UpdateClientInput, Client>(inputDto, centity);
_context.update();