¿Cada relación de datos centrales tiene que tener un inverso?

Digamos que tengo dos clases de Entidad: SocialApp y SocialAppType

En SocialApp tengo un atributo: appURL y una relación: type .

En SocialAppType tengo tres atributos: baseURL , name y favicon .

El destino del type relación SocialApp es un único registro en SocialAppType .

Como ejemplo, para múltiples cuentas de Flickr, habría una cantidad de registros de SocialApp , con cada registro sosteniendo un enlace a la cuenta de una persona. Habría un registro SocialAppType para el tipo “Flickr”, al que SocialApp todos los registros de SocialApp .

Cuando construyo una aplicación con este esquema, recibo una advertencia de que no existe una relación inversa entre SocialAppType y SocialApp .

  /Users/username/Developer/objc/TestApp/TestApp.xcdatamodel:SocialApp.type: warning: SocialApp.type -- relationship does not have an inverse 

¿Necesito un inverso, y por qué?

En la práctica, no he tenido ninguna pérdida de datos debido a que no tengo una inversa, al menos que yo sepa. Un rápido Google sugiere que los use:

Una relación inversa no solo hace que las cosas estén más ordenadas, en realidad, es utilizada por Core Data para mantener la integridad de los datos.

– Cocoa Dev Central

Por lo general, debe modelar las relaciones en ambas direcciones y especificar las relaciones inversas de manera apropiada. Core Data utiliza esta información para garantizar la coherencia del gráfico de objeto si se realiza un cambio (consulte “Manipulación de relaciones y la integridad del gráfico de objeto”). Para una discusión de algunas de las razones por las cuales es posible que no desee modelar una relación en ambas direcciones, y algunos de los problemas que podrían surgir si no lo hace, consulte “Relaciones unidireccionales”.

– Guía de progtwigción de datos básicos

La documentación de Apple tiene un gran ejemplo que sugiere una situación en la que podría tener problemas al no tener una relación inversa. Vamos a mapearlo en este caso.

Suponga que lo modeló de la siguiente manera: enter image description here

Tenga en cuenta que tiene una relación única llamada ” tipo “, desde SocialApp a SocialAppType . La relación no es opcional y tiene una regla de eliminación “denegar” .

Ahora considera lo siguiente:

 SocialApp *socialApp; SocialAppType *appType; // assume entity instances correctly instantiated [socialApp setSocialAppType:appType]; [managedObjectContext deleteObject:appType]; BOOL saved = [managedObjectContext save:&error]; 

Lo que esperamos es dejar de guardar este contexto, ya que hemos establecido la regla de eliminación como Denegar, mientras que la relación no es opcional.

Pero aquí el guardado tiene éxito.

La razón es que no hemos establecido una relación inversa . Por eso, la instancia de socialApp no ​​se marca como cambiada cuando se elimina appType. Por lo tanto, no ocurre ninguna validación para socialApp antes de guardar (no se requiere validación porque no se produjo ningún cambio). Pero en realidad sucedió un cambio. Pero no se refleja

Si recordamos appType por

 SocialAppType *appType = [socialApp socialAppType]; 

appType es nulo.

Extraño, ¿no es así? Obtenemos nil por un atributo no opcional?

Entonces no tiene problemas si ha establecido una relación inversa. De lo contrario, debe forzar la validación escribiendo el código de la siguiente manera.

 SocialApp *socialApp; SocialAppType *appType; // assume entity instances correctly instantiated [socialApp setSocialAppType:appType]; [managedObjectContext deleteObject:appType]; [socialApp setValue:nil forKey:@"socialAppType"] BOOL saved = [managedObjectContext save:&error]; 

Parafrasearé la respuesta definitiva que encontré en Más desarrollo de iPhone 3 por Dave Mark y Jeff LeMarche.

Apple generalmente recomienda que siempre cree y especifique lo contrario, incluso si no usa la relación inversa en su aplicación. Por esta razón, te avisa cuando no puedes proporcionar un inverso.

No se requiere que las relaciones tengan una inversa, porque hay algunos escenarios en los que la relación inversa podría perjudicar el rendimiento. Por ejemplo, supongamos que la relación inversa contiene una cantidad extremadamente grande de objetos. La eliminación de lo inverso requiere iterar sobre el conjunto que representa el rendimiento inverso y debilitante.

Pero a menos que tenga una razón específica para no hacerlo, modele lo contrario . Ayuda a Core Data a garantizar la integridad de los datos. Si se encuentra con problemas de rendimiento, es relativamente fácil eliminar la relación inversa más adelante.

La mejor pregunta es, “¿hay alguna razón para no tener una inversa”? Core Data es realmente un marco de gestión de gráficos de objeto, no un marco de persistencia. En otras palabras, su trabajo es administrar las relaciones entre los objetos en el gráfico de objetos. Las relaciones inversas hacen esto mucho más fácil. Por esa razón, Core Data espera relaciones inversas y está escrito para ese caso de uso. Sin ellos, usted tendrá que administrar la consistencia del gráfico del objeto usted mismo. En particular, es probable que Core-Data corrompa a muchas relaciones sin una relación inversa, a menos que trabaje muy duro para que todo funcione. El costo en términos de tamaño del disco para las relaciones inversas realmente es insignificante en comparación con el beneficio que le proporciona.

Hay al menos un escenario en el que se puede presentar un buen caso para una relación de datos central sin un inverso: cuando ya hay otra relación de datos centrales entre los dos objetos, que manejará el mantenimiento del gráfico del objeto.

Por ejemplo, un libro contiene muchas páginas, mientras que una página está en un libro. Esta es una relación de dos vías de muchos a uno. Eliminar una página simplemente anula la relación, mientras que eliminar un libro también eliminará la página.

Sin embargo, también puede desear rastrear la página actual que se lee para cada libro. Esto se puede hacer con una propiedad “currentPage” en la página , pero luego necesita otra lógica para asegurarse de que solo una página del libro esté marcada como la página actual en cualquier momento. En cambio, al hacer una relación de CurrentPage desde Book a una sola página, se asegurará de que siempre haya solo una página actual marcada, y además de que se puede acceder fácilmente a esta página con una referencia al libro con book.currentPage.

Presentación gráfica de la relación de datos centrales entre libros y páginas

¿Cuál sería la relación recíproca en este caso? Algo en gran parte sin sentido. “myBook” o similar podría agregarse en la otra dirección, pero contiene solo la información que ya figura en la relación “libro” para la página, y por lo tanto crea sus propios riesgos. Quizás en el futuro, cambie la forma en que usa una de estas relaciones, lo que generará cambios en la configuración de datos básicos. Si page.myBook se ha utilizado en algunos lugares donde page.book debería haberse utilizado en el código, podría haber problemas. Otra forma de evitar esto de manera proactiva también sería no exponer myBook en la subclase NSManagedObject que se utiliza para acceder a la página. Sin embargo, se puede argumentar que es más simple no modelar lo inverso en primer lugar.

En el ejemplo descrito, la regla de eliminación para la relación de la página actual debe establecerse en “Sin acción” o “Cascada”, ya que no hay relación recíproca con “Anular”. (Cascade implica que estás extrayendo cada página del libro mientras lo lees, pero eso podría ser cierto si estás particularmente frío y necesitas combustible.)

Cuando se puede demostrar que la integridad del gráfico de objetos no está en riesgo, como en este ejemplo, y se mejora la complejidad y la mantenibilidad del código, se puede argumentar que una relación sin inversa puede ser la decisión correcta.

Si bien los documentos no parecen requerir una inversión, simplemente resolví un escenario que de hecho resultó en “pérdida de datos” al no tener una inversión. Tengo un objeto de informe que tiene una relación de muchos en objetos reportables. Sin la relación inversa, cualquier cambio en la relación de muchos se perdió al relanzar. Después de inspeccionar la depuración de Core Data, era evidente que, aunque estaba guardando el objeto de informe, nunca se realizaron las actualizaciones del gráfico de objeto (relaciones). Agregué un inverso, aunque no lo uso, y listo, funciona. Por lo tanto, podría no decirse que sea necesario, pero las relaciones sin inversos definitivamente pueden tener efectos secundarios extraños.