Я получаю данные фильма из внешнего API. На первом этапе я буду чистить каждый фильм и вставлять его в свою базу данных. На втором этапе я буду периодически обновлять свою базу данных, используя API «Изменения» API, который я могу запросить, чтобы увидеть, какие фильмы изменили свою информацию.
Мой слой ORM - это Entity-Framework. Класс Movie выглядит следующим образом:
class Movie
{
public virtual ICollection<Language> SpokenLanguages { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
Проблема возникает, когда у меня есть фильм, который необходимо обновить: моя база данных будет думать об отслеживаемом объекте и новом, который я получаю из вызова API обновления, как разные объекты, игнорируя .Equals()
.
Это вызывает проблему, потому что, когда я сейчас пытаюсь обновить базу данных обновленным фильмом, он вставит ее вместо обновления существующего фильма.
Раньше у меня была эта проблема с языками, и мое решение состояло в том, чтобы искать присоединенные языковые объекты, отсоединять их от контекста, перемещать их PK в обновленный объект и прикреплять их к контексту. Когда SaveChanges()
теперь выполняется, это по существу заменит его.
Это довольно вонючий подход, потому что, если я продолжу этот подход к своему Movie
объекту, это означает, что мне придется отделить фильм, языки, жанры и ключевые слова, найти каждый из них в базе данных, перенести их идентификаторы и вставить новые объекты.
Есть ли способ сделать это более элегантно? В идеале я просто хочу передать обновленный фильм в контекст, и он должен выбрать правильный фильм для обновления на основе Equals()
метода, обновить все его поля и для каждого сложного объекта: снова использовать существующую запись на основе своего собственного Equals()
метода и вставить, если это еще не существует.
Я могу пропустить отсоединение / присоединение, предоставляя .Update()
методы для каждого сложного объекта, которые я могу использовать в сочетании с извлечением всех прикрепленных объектов, но для этого все равно потребуется извлекать каждый существующий объект, чтобы затем обновить его.
источник
id
и фильмы из внешнего API сопоставляются с локальными с помощью поляtmdbid
. Я не могу получить все объекты, которые необходимо обновить за один вызов, потому что они касаются фильмов, жанров, языков, ключевых слов и т. Д. Каждый из них имеет PK и может уже существовать в базе данных.Ответы:
Я не нашел того, на что надеялся, но нашел улучшение по сравнению с существующей последовательностью select-detach-update-attach.
Метод расширения
AddOrUpdate(this DbSet)
позволяет вам делать именно то, что я хочу: вставить, если его там нет, и обновить, если он нашел существующее значение. Я не осознавал, что использую это раньше, так как на самом деле видел только то, как оно используется вseed()
методе в сочетании с миграциями. Если есть какая-то причина, по которой я не должен использовать это, дайте мне знать.Что-то полезное для заметки: существует перегрузка, которая позволяет вам конкретно выбирать, как должно быть определено равенство. Здесь я мог бы использовать свой,
TMDbId
но вместо этого я решил полностью игнорировать свой собственный идентификатор и вместо этого использовать PK в TMDbId в сочетании сDatabaseGeneratedOption.None
. Я использую этот подход также в каждой подгруппе, где это уместно.Интересная часть источника :
именно так данные на самом деле обновляются под капотом.
Все, что осталось, - это вызывать
AddOrUpdate
каждый объект, на который я хочу повлиять:Он не так чист, как я надеялся, поскольку мне приходится вручную указывать каждый фрагмент моего объекта, который нужно обновить, но он настолько близок, насколько это возможно.
Связанное чтение: /programming/15336248/entity-framework-5-updating-a-record
Обновить:
Оказывается, мои тесты были недостаточно строгими. После использования этой техники я заметил, что хотя новый язык был добавлен, он не был связан с фильмом. в таблице «многие ко многим». Это известная, но, казалось бы, проблема с низким приоритетом, и, насколько я знаю, ее не удалось устранить.
В конце концов я решил пойти на подход, где у меня есть
Update(T)
методы для каждого типа и следовать этой последовательности событий:Update()
метод, чтобы обновить его новыми значениямиЭто много ручной работы, и это уродливо, так что пройдёт еще несколько рефакторингов, но теперь мои тесты показывают, что это должно работать для более строгих сценариев.
После очистки я теперь использую этот метод:
Это позволяет мне называть это так и вставлять / обновлять базовые коллекции:
Обратите внимание, как я переназначаю полученное значение исходному корневому объекту: теперь оно связано с каждым присоединенным объектом. Обновление корневого объекта (фильма) выполняется так же:
источник
Так как вы имеете дело с различными полями,
id
иtmbid
я предлагаю обновить API, чтобы создать единый и отдельный индекс всей информации, такой как жанры, языки, ключевые слова и т. Д., А затем сделать запрос индекса и проверить информацию, а не собирать вся информация о конкретном объекте в вашем классе Movie.источник