Используя 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();
Таким образом, вся иерархия дочерних объектов будет соответствующим образом обновлена в базе данных.