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

Entity Framework 4, объекты POCO и ASP.Net MVC2. У меня есть много разных отношений, скажем между BlogPost и тегами. Это означает, что в моем 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. (Обновление: теперь я попытался сделать в том же экземпляре контекста, а также не удалось.)

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

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

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

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

Ответ 1

Попробуем это так:

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

Edit:

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

Проблема фона

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

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

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

Решение

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

Я приведу это предположение с единственной ссылкой на другой вопрос Entity Framework 4.0 Recipes. Если он говорит, что автоматическое слияние объектных графов не поддерживается, я верю ему.

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

Изменить 2:

Как вы можете видеть, это уже было добавлено в MS Connect в 2007 году. MS закрыла его как что-то, что нужно сделать в следующей версии но на самом деле ничего не было сделано для улучшения этого пробела, кроме STE.

Ответ 2

У меня есть решение проблемы, описанной выше Ладиславом. Я создал метод расширения для 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

Ответ 3

Я знаю, что это поздно для OP, но поскольку это очень распространенная проблема, я опубликовал ее, если она служит кому-то другому. Я обсуждал эту проблему, и я думаю, что у меня довольно простое решение, что я делаю:

  • Сохранить основной объект (например, блоги), установив его состояние в Модифицированное.
  • Запросить базу данных для обновленного объекта, включая коллекции, которые мне нужно обновить.
  • Запросить и преобразовать .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();

Он работает даже для нескольких отношений

Ответ 4

Команда Entity Framework знает, что это проблема юзабилити и планирует решить ее после EF6.

В команде Entity Framework:

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

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

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