Обновление отношений при сохранении изменений объектов POCO EF4

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). Позже я возвращаю обновленный BlogPost с измененными свойствами и измененными отношениями. Например, у него были теги "A", "B" и "C", а новые теги - "C" и "D". В моем конкретном примере нет новых тегов, и свойства тегов никогда не меняются, поэтому единственное, что нужно сохранить, - это измененные отношения. Теперь мне нужно сохранить это в другом ObjectContext. (Обновление: теперь я попытался сделать в том же контексте экземпляр и тоже не удалось.)

Проблема: я не могу сохранить отношения должным образом. Я перепробовал все, что нашел:

  • Controller.UpdateModel и Controller.TryUpdateModel не работают.
  • Получение старого BlogPost из контекста, а затем изменение коллекции не работает. (с различными методами из следующего пункта)
  • Это, вероятно, будет работать, но я надеюсь, что это просто обходной путь, а не решение:(.
  • Пробовал прикреплять / добавлять / изменять ObjectState функции для BlogPost и / или тегов во всех возможных комбинациях. Не удалось.
  • Это похоже на то, что мне нужно, но это не работает (я пытался это исправить, но не смог решить мою проблему).
  • Пробовал ChangeState /Add/ Attach /... объекты отношения контекста. Не удалось.

"Не работает" в большинстве случаев означает, что я работал над данным "решением", пока он не выдаст никаких ошибок и не сохранит хотя бы свойства BlogPost. То, что происходит с отношениями, варьируется: обычно теги снова добавляются в таблицу тегов с новыми PK, и сохраненный BlogPost ссылается на них, а не на исходные. Конечно, возвращаемые теги имеют PK, и перед методами сохранения / обновления я проверяю PK, и они равны тем, которые есть в базе данных, поэтому, вероятно, EF считает, что они являются новыми объектами, а эти PK - временными.

Проблема, о которой я знаю, и может сделать невозможным автоматическое простое решение: при изменении коллекции объекта POCO это должно происходить с помощью вышеупомянутого свойства виртуальной коллекции, потому что тогда трюк FixupCollection обновит обратные ссылки на другом конце отношения многих ко многим. Однако, когда View "возвращает" обновленный объект BlogPost, этого не произошло. Это означает, что, возможно, не существует простого решения моей проблемы, но это очень огорчило бы меня, и я бы ненавидел триумф EF4-POCO-MVC:(. Это также означало бы, что EF не может сделать это в среде MVC, в зависимости от того, что Используются типы объектов EF4:(. Я думаю, что отслеживание изменений на основе снимка должно выяснить, что измененный BlogPost связан с тегами с существующими PK.

Кстати: я думаю, что та же проблема происходит с отношениями один-ко-многим (Google и мой коллега так говорят). Я попробую дома, но даже если это сработает, это не поможет мне в моих шести отношениях "многие ко многим" в моем приложении:(.

5 ответов

Решение

Давайте попробуем это так:

  • Присоедините BlogPost к контексту. После прикрепления объекта к контексту состояние объекта, всех связанных объектов и всех отношений устанавливается в значение Без изменений.
  • Используйте context.ObjectStateManager.ChangeObjectState, чтобы установить для вашего BlogPost значение Modified.
  • Итерация по коллекции тегов
  • Используйте context.ObjectStateManager.ChangeRelationshipState, чтобы установить состояние для связи между текущим тегом и BlogPost.
  • Сохранить изменения

Редактировать:

Я думаю, что один из моих комментариев дал вам ложную надежду, что EF сделает слияние за вас. Я много играл с этой проблемой, и мой вывод гласит, что EF не сделает этого за вас. Я думаю, вы также нашли мой вопрос на MSDN. На самом деле таких вопросов в Интернете достаточно. Проблема в том, что неясно указано, как бороться с этим сценарием. Итак, давайте посмотрим на проблему:

Проблемный фон

EF необходимо отслеживать изменения в сущностях, чтобы персистентность знала, какие записи необходимо обновить, вставить или удалить. Проблема заключается в том, что ObjectContext отвечает за отслеживание изменений. ObjectContext может отслеживать изменения только для прикрепленных объектов. Объекты, созданные вне ObjectContext, вообще не отслеживаются.

Описание проблемы

Исходя из приведенного выше описания, мы можем четко заявить, что EF больше подходит для связанных сценариев, где сущность всегда привязана к контексту - типично для приложения WinForm. Для веб-приложений требуется отключенный сценарий, когда контекст закрывается после обработки запроса, а содержимое объекта передается как HTTP-ответ клиенту. Следующий HTTP-запрос предоставляет измененный контент объекта, который должен быть воссоздан, присоединен к новому контексту и сохранен. Отдых обычно происходит вне контекста контекста (многоуровневая архитектура с постоянным невежеством).

Решение

Так как бороться с таким отключенным сценарием? При использовании классов POCO у нас есть 3 способа отслеживания изменений:

  • Снимок - требует тот же контекст = бесполезно для отключенного сценария
  • Прокси-серверы динамического отслеживания - требуется тот же контекст = бесполезно для отключенного сценария
  • Ручная синхронизация.

Ручная синхронизация на одном объекте - простая задача. Вам просто нужно присоединить сущность и вызвать AddObject для вставки, DeleteObject для удаления или установить состояние в ObjectStateManager в значение Modified для обновления. Настоящая боль возникает, когда вам приходится иметь дело с графом объектов, а не с одной сущностью. Эта боль еще хуже, когда вам приходится иметь дело с независимыми ассоциациями (теми, которые не используют свойство внешнего ключа) и отношениями многие ко многим. В этом случае вы должны вручную синхронизировать каждую сущность в графе объектов, а также каждое отношение в графе объектов.

Ручная синхронизация предлагается в качестве решения в документации MSDN: Присоединение и отсоединение объектов гласят:

Объекты присоединяются к контексту объекта в неизменном состоянии. Если вам нужно изменить состояние объекта или отношения, потому что вы знаете, что ваш объект был изменен в отключенном состоянии, используйте один из следующих методов.

Упомянутыми методами являются ChangeObjectState и ChangeRelationshipState из ObjectStateManager = отслеживание изменений вручную. Аналогичное предложение есть в другой статье по документации MSDN: Определение и управление отношениями гласит:

Если вы работаете с отключенными объектами, вы должны вручную управлять синхронизацией.

Более того, в блоге, посвященном EF v1, есть пост, в котором критикуется именно это поведение EF.

Причина решения

EF имеет много "полезных" операций и настроек, таких как Refresh, Load, ApplyCurrentValues, ApplyOriginalValues, MergeOption и т. Д. Но по моим исследованиям все эти функции работают только для одной сущности и влияют только на скалярные свойства (= не навигационные свойства и отношения). Я скорее не тестирую эти методы со сложными типами, вложенными в сущность.

Другое предлагаемое решение

Вместо реальной функциональности слияния команда EF предоставляет нечто, называемое " Сопровождение сущностей" (STE), которое не решает проблему. Прежде всего, STE работает, только если один и тот же экземпляр используется для всей обработки. В веб-приложении это не так, если вы не сохраняете экземпляр в состоянии просмотра или сеанса. В связи с этим я очень недоволен использованием EF и собираюсь проверить возможности NHibernate. Первое наблюдение говорит о том, что NHibernate, возможно, обладает такой функциональностью.

Заключение

Я закончу эти предположения одной ссылкой на другой связанный вопрос на форуме MSDN. Проверьте ответ Зеешана Хирани. Он является автором Entity Framework 4.0 Recipes. Если он говорит, что автоматическое объединение графов объектов не поддерживается, я верю ему.

Но все же есть вероятность, что я совершенно не прав, и в EF существует некоторая функция автоматического слияния.

Изменить 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, но так как это очень распространенная проблема, я разместил ее на тот случай, если она послужит кому-то еще. Я возился с этой проблемой и думаю, что нашел довольно простое решение, которое я делаю так:

  1. Сохраните основной объект (например, Blogs), установив его состояние в значение Modified.
  2. Запросите базу данных для обновленного объекта, включая коллекции, которые мне нужно обновить.
  3. Запрос и преобразование.ToList() сущностей, которые я хочу включить в свою коллекцию.
  4. Обновите коллекцию (ы) основного объекта в Список, полученный с шага 3.
  5. Сохранить изменения();

В следующем примере "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:

Это проблема юзабилити, о которой мы знаем, и о которой мы думаем и планируем проделать дополнительную работу после EF6. Я создал этот рабочий элемент для отслеживания проблемы: http://entityframework.codeplex.com/workitem/864 элемент также содержит ссылку на голосовой элемент пользователя для этого - я призываю вас проголосовать за него, если у вас есть еще не сделано

Если это повлияет на вас, проголосуйте за функцию на

http://entityframework.codeplex.com/workitem/864

Все ответы были великолепны, чтобы объяснить проблему, но ни один из них не решил проблему для меня.

Я обнаружил, что если я не использовал отношения в родительской сущности, а просто добавил и удалил дочерние сущности, все работало просто отлично.

Извините за VB, но это то, в чем написан проект, в котором я работаю.

Родительский объект "Отчет" имеет отношение "один ко многим" к "ReportRole" и имеет свойство "ReportRoles". Новые роли передаются через запятую из вызова Ajax.

Первая строка удалит все дочерние объекты, и если бы я использовал "report.ReportRoles.Remove(f)" вместо "db.ReportRoles.Remove(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}))
Другие вопросы по тегам