Обновление связей при сохранении изменений объектов EF4 POCO
Entity Framework 4, объекты POCO и ASP.Net MVC2. У меня есть много ко многим отношениям, скажем, между объектами BlogPost и Tag. Это означает, что в моем T4 сгенерированном классе Poco BlogPost у меня есть:
public virtual ICollection<Tag> Tags {
// getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;
я запрашиваю BlogPost и связанные Теги из экземпляра ObjectContext и отправляю его на другой уровень (просмотр в приложении MVC). Позже я возвращаю обновленный блог с измененными свойствами и измененными отношениями. Например, у него были теги "A" " B " и "C", а новые теги - " C "и"D". В моем конкретном примере нет новых тегов, и свойства тегов никогда не меняются, поэтому единственное, что должно быть сохранено, - это измененные отношения. Теперь мне нужно сохранить это в другом ObjectContext. (Обновление: теперь я попытался сделать в том же экземпляре контекста, а также потерпел неудачу.)
проблема: я не могу сделать это сохранить отношения должным образом. Я перепробовал все, что нашел:
- .UpdateModel и Контроллер.TryUpdateModel не работает.
- получение старого блога из контекста, а затем изменение коллекции не работает. (с различными методами из следующего пункта)
- Это, вероятно, будет работать, но я надеюсь, что это просто обходной путь, а не решение :(.
- пробовал функции Attach/Add/ChangeObjectState для BlogPost и / или тегов во всех возможных комбинациях. Неудачный.
- этой похоже, что мне нужно, но это не работает (Я пытался исправить это, но не могу для моей проблемы).
- Пробовал ChangeState / Add/Attach/... объекты отношений контекста. Неудачный.
"Не работает "означает в большинстве случаев, что я работал над данным" решением", пока он не выдает ошибок и не сохраняет хотя бы свойства BlogPost. То, что происходит с отношениями, варьируется: обычно Теги снова добавляются в таблицу тегов с новыми PKs, а сохраненный BlogPost ссылается на них, а не на оригинальные. Конечно возвращенные Теги имеют PKs, и перед методами сохранения/обновления я проверяю PKs, и они равны тем, что есть в базе данных, поэтому, вероятно, EF думает, что они являются новыми объектами, а эти PKs являются временными.
проблема, о которой я знаю, и может сделать невозможным найти автоматическое простое решение: когда коллекция объекта POCO изменяется, это должно произойти с помощью вышеупомянутого свойства виртуальной коллекции, потому что тогда трюк FixupCollection обновит обратное ссылки на другой конец отношений "многие ко многим". Однако, когда представление "возвращает" обновленный объект BlogPost, этого не произошло. Это означает, что, возможно, нет простого решения моей проблемы, но это сделало бы меня очень грустным, и я бы ненавидел ef4-POCO-MVC triumph :(. Также это означало бы, что EF не может сделать это в среде MVC независимо от того, какие типы объектов EF4 используются :(. Я думаю, что отслеживание изменений на основе моментального снимка должно выяснить, что измененный BlogPost имеет отношения к Теги с существующими PKs.
кстати: я думаю, что та же проблема происходит с отношениями "один ко многим" (google и мой коллега так говорят). Я дам ему попробовать дома, но даже если это работает, что не помогает мне в моих шести много-ко-многим отношениям в моем приложении :(.
5 ответов:
давайте попробуем так:
- прикрепить BlogPost к контексту. После присоединения объекта к контексту состояние объекта, всех связанных объектов и всех отношений устанавливается неизменным.
- использовать контекст.ObjectStateManager.ChangeObjectState, чтобы установить свой блог в Modified
- перебирать коллекцию тегов
- использовать контекст.ObjectStateManager.ChangeRelationshipState для установки состояния связи между текущим тегом и Блогпост.
- SaveChanges
Edit:
Я думаю, что один из моих комментариев дал вам ложную надежду, что EF сделает слияние для вас. Я много играл с этой проблемой, и мой вывод говорит, что EF не будет делать этого для вас. Я думаю, что вы также нашли мой вопрос на MSDN. На самом деле таких вопросов в интернете очень много. Проблема заключается в том, что не ясно указано, как бороться с этим сценарием. Так что давайте посмотрим о проблеме:
история вопроса
EF должен отслеживать изменения в сущностях, чтобы Persistence знал, какие записи должны быть обновлены, вставлены или удалены. Проблема в том, что это ObjectContext ответственность отслеживать изменения. ObjectContext может отслеживать изменения только для вложенных объектов. Сущности, созданные вне ObjectContext, вообще не отслеживаются.
проблема описание
основываясь на приведенном выше описании, мы можем четко заявить, что EF более подходит для связанных сценариев, где сущность всегда привязана к контексту, типичному для приложения WinForm. Для веб-приложений требуется отключенный сценарий, в котором контекст закрывается после обработки запроса, а содержимое сущности передается клиенту в качестве HTTP-ответа. Следующий HTTP-запрос предоставляет измененное содержимое сущности, которое должно быть воссоздано, присоединено к новому контексту и сохранено. Рекреация обычно происходит вне области контекста (многоуровневая архитектура с сохранением ignorace).
решение
Так как же бороться с таким отключенным сценарием? При использовании классов POCO у нас есть 3 способа борьбы с отслеживанием изменений:
- снимок-требуется тот же контекст = бесполезно для отключенного сценария
- динамическое отслеживание прокси-требуется тот же контекст = бесполезно для отключенного сценарий
- ручной синхронизации.
ручная синхронизация на одном объекте-это простая задача. Вам просто нужно прикрепить объект и вызвать AddObject для вставки, DeleteObject для удаления или установить состояние в ObjectStateManager для изменения для обновления. Настоящая боль приходит, когда вам приходится иметь дело с графом объектов, а не с одной сущностью. Эта боль еще хуже, когда вам приходится иметь дело с независимыми ассоциациями (те, которые не используют свойство внешнего ключа) и многие другие ко многим отношениям. В этом случае вам нужно вручную синхронизировать каждую сущность в графе объектов, но также и каждое отношение в графе объектов.
ручная синхронизация предлагается в качестве решения по документации MSDN:прикрепление и отсоединение объектов говорит:
объекты прикреплены к объекту контекст в неизмененном состоянии. Если вы необходимо изменить состояние объекта или отношения, потому что вы знаете, что объект был изменен в отсоединенное состояние, используйте один из следующая методика.
упомянутые методы ChangeObjectState и ChangeRelationshipState ObjectStateManager = отслеживание изменений вручную. Аналогичное предложение содержится в другой статье документации MSDN:определение и управление отношениями с говорит:
Если вы работаете с отключенным объекты, которыми необходимо управлять вручную синхронизация.
кроме того,блог пост связанные с EF v1, которые критикуют именно это поведение EF.
причина решение
EF имеет много "полезных" операций и настроек, таких как обновить,загрузить,ApplyCurrentValues,ApplyOriginalValues,MergeOption etc. Но по моему исследованию все эти функции работают только для одного объекта и влияют только на скалярные свойства (=не свойства навигации и отношения). Я скорее не тестирую эти методы со сложными типами, вложенными в сущность.
другое предложенное решение
вместо реальной функциональности слияния EF team предоставляет что-то под названием Self Tracking Entities (STE) которые не решают проблему. Прежде всего STE работает только в том случае, если один и тот же экземпляр используется для всей обработки. В веб-приложении это не так, если вы не храните экземпляр в состоянии представления или сеанса. Из-за этого я очень недовольный от использования EF, и я собираюсь проверить особенности NHibernate. Первое наблюдение говорит, что NHibernate, возможно, имеет такой функциональность.
вывод
Я закончу это предположение с одной ссылкой на другую вопрос на форуме MSDN. Проверьте ответ Зишана Хирани. Он является автором Entity Framework 4.0 Recipes. Если он говорит, что автоматическое слияние графов объектов не поддерживается, я поверить ему.
но все же есть вероятность, что я совершенно не прав, и некоторые функции автоматического слияния существуют в EF.
Edit 2:
Как вы можете видеть, это уже было добавлено в MS Connect как предложение в 2007 году. MS закрыла его как что-то, что нужно сделать в следующей версии, но на самом деле ничего не было сделано для улучшения этого разрыва, кроме STE.
У меня есть решение проблемы, которая была описана выше Ладислава. Я создал метод расширения для DbContext, который будет автоматически выполнять добавление/обновление / удаление на основе различия предоставленного графика и сохраненного графика.
в настоящее время с помощью Entity Framework вам нужно будет выполнить обновления контактов вручную, проверить, является ли каждый контакт новым и добавить, проверить, обновлен ли и редактировать, проверить, удален ли он, а затем удалить его из базы данных. Как только вам нужно сделать это для нескольких различных агрегатов в большой системе, вы начинаете понимать, что должен быть лучший, более общий способ.
пожалуйста, взгляните и посмотрите, может ли это помочь http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
вы можете перейти прямо к коду здесь https://github.com/refactorthis/GraphDiff
Я знаю, что это поздно для OP, но так как это очень распространенная проблема, я опубликовал это в случае, если он служит кому-то еще. Я играл с этой проблемой, и я думаю, что у меня есть довольно простое решение, что я делаю:
- сохраните основной объект (например, блоги), установив его состояние в Modified.
- запрос базы данных для обновленного объекта, включая коллекции, которые мне нужно обновить.
- запрос и преобразовать .ToList () сущности, которые я хочу, чтобы моя коллекция включать.
- обновите коллекцию(ы) основного объекта в список, который я получил из Шага 3.
- SaveChanges ();
в следующем примере "dataobj" и "_categories" - это параметры, полученные моим контроллером "dataobj" - это мой основной объект, а "_categories" - это IEnumerable, содержащий идентификаторы категорий, выбранных пользователем в представлении.
db.Entry(dataobj).State = EntityState.Modified; db.SaveChanges(); dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id); var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null; dataobj.Categories = it; db.SaveChanges();
Он даже работает для нескольких отношений
команда Entity Framework знает, что это проблема удобства использования и планирует решить ее после EF6.
из команды Entity Framework:
Это вопрос юзабилити, о котором мы знаем, и это то, о чем мы думали и планируем сделать больше работы над post-EF6. Я создал этот рабочий элемент для отслеживания проблемы:http://entityframework.codeplex.com/workitem/864 рабочий элемент также содержит ссылку на голосовой элемент пользователя для я призываю вас проголосовать за него, если вы еще этого не сделали.
Если это влияет на вас, голосуйте за объект в
все ответы были великолепны, чтобы объяснить проблему, но ни один из них не решил проблему для меня.
Я обнаружил, что если я не использовал отношения в родительской сущности, но просто добавил и удалил дочерние сущности, все работало просто отлично.
извините за VB, но именно в этом написан проект, в котором я работаю.
родительская сущность " Report "имеет отношение" один ко многим "к"ReportRole" и имеет свойство "ReportRoles". Этот новые роли передаются через строку, разделенную запятой, из вызова Ajax.
в первой строке будут удалены все дочерние сущности, и если я использовал "отчет.Репортаж.Удалите(f) "вместо" db.Репортаж.Удалить(f) " я бы получил ошибку.
report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f)) Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(",")) newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))