Recupero datos de películas de una API externa. En una primera fase, rasparé cada película y la insertaré en mi propia base de datos. En una segunda fase, actualizaré periódicamente mi base de datos utilizando la API "Cambios" de la API, que puedo consultar para ver qué películas han cambiado su información.
Mi capa ORM es Entity-Framework. La clase de película se ve así:
class Movie
{
public virtual ICollection<Language> SpokenLanguages { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
El problema surge cuando tengo una película que necesita ser actualizada: mi base de datos pensará en el objeto que se está rastreando y en el nuevo que recibo de la llamada API de actualización como objetos diferentes, sin tener en cuenta .Equals()
.
Esto causa un problema porque cuando ahora trato de actualizar la base de datos con la película actualizada, la insertará en lugar de actualizar la película existente.
Tuve este problema antes con los idiomas y mi solución fue buscar los objetos de lenguaje adjuntos, separarlos del contexto, mover su PK al objeto actualizado y adjuntarlo al contexto. Cuando SaveChanges()
ahora se ejecuta, esencialmente lo reemplazará.
Este es un enfoque bastante maloliente porque si continúo este enfoque hacia mi Movie
objeto, significa que tendré que separar la película, los idiomas, los géneros y las palabras clave, buscar cada uno en la base de datos, transferir sus ID e insertar el nuevos objetos
¿Hay alguna manera de hacer esto con más elegancia? Idealmente, solo quiero pasar la película actualizada al contexto y hacer que seleccione la película correcta para actualizar según el Equals()
método, actualice todos sus campos y para cada objeto complejo: use el registro existente nuevamente según su propio Equals()
método e inserte si aún no existe
Puedo omitir la separación / fijación proporcionando .Update()
métodos en cada objeto complejo que puedo usar en combinación para recuperar todos los objetos adjuntos, pero esto aún requerirá que recupere cada objeto existente para luego actualizarlo.
fuente
id
y las películas de la API externa coinciden con las locales que usan el campotmdbid
. No puedo recuperar todas las entidades que deben actualizarse en una sola llamada porque se trata de películas, géneros, idiomas, palabras clave, etc. Cada una de estas tiene un PK y puede que ya exista en la base de datos.Respuestas:
No encontré lo que esperaba, pero sí encontré una mejora con respecto a la secuencia existente select-detach-update-attach.
El método de extensión le
AddOrUpdate(this DbSet)
permite hacer exactamente lo que quiero hacer: insertar si no está allí y actualizar si encuentra un valor existente. No me di cuenta de usar esto antes, ya que realmente solo he visto que se use en elseed()
método en combinación con Migraciones. Si hay alguna razón por la que no debería usar esto, avíseme.Algo útil a tener en cuenta: hay una sobrecarga disponible que le permite seleccionar específicamente cómo se debe determinar la igualdad. Aquí podría haber usado mi
TMDbId
pero en lugar de eso opté por simplemente ignorar mi propia ID por completo y usar una PK en TMDbId combinada conDatabaseGeneratedOption.None
. También uso este enfoque en cada subcolección, cuando corresponde.Parte interesante de la fuente :
que es cómo se actualizan los datos realmente bajo el capó.
Todo lo que queda es llamar
AddOrUpdate
a cada objeto que quiero que se vea afectado por esto:No es tan limpio como esperaba, ya que tengo que especificar manualmente cada pieza de mi objeto que necesita actualizarse, pero está lo más cerca posible.
Lectura relacionada: /programming/15336248/entity-framework-5-updating-a-record
Actualizar:
Resulta que mis pruebas no fueron lo suficientemente rigurosas. Después de usar esta técnica, noté que si bien se agregó el nuevo idioma, no estaba conectado a la película. en la tabla de muchos a muchos. Este es un problema conocido pero aparentemente de baja prioridad y, hasta donde sé, no se ha solucionado.
Al final decidí ir al enfoque donde tengo
Update(T)
métodos en cada tipo y seguir esta secuencia de eventos:Update()
método para actualizarlo con los nuevos valores.Es mucho trabajo manual y es feo, por lo que pasará por algunas refactorizaciones más, pero ahora mis pruebas indican que debería funcionar para escenarios más rigurosos.
Después de limpiarlo más, ahora uso este método:
Esto me permite llamarlo así e insertar / actualizar las colecciones subyacentes:
Observe cómo reasigno el valor recuperado al objeto raíz original: ahora está conectado a cada objeto adjunto. La actualización del objeto raíz (la película) se realiza de la misma manera:
fuente
Dado que se trata de diferentes campos
id
ytmbid
, sugiero que actualice la API para hacer un índice único e independiente de toda la información, como géneros, idiomas, palabras clave, etc. Y luego haga una llamada para indexar y verificar la información en lugar de recopilar toda la información sobre un objeto específico en tu clase de película.fuente