Chaque relation Core Data doit-elle avoir un Inverse?

150

Disons que j'ai deux classes d'entité: SocialAppetSocialAppType

Dans SocialAppje un attribut: appURLet une relation: type.

Dans SocialAppTypeJ'ai trois attributs: baseURL, nameet favicon.

La destination de la SocialApprelation typeest un enregistrement unique dans SocialAppType.

Par exemple, pour plusieurs comptes Flickr, il y aurait un certain nombre d' SocialAppenregistrements, chaque enregistrement contenant un lien vers le compte d'une personne. Il y aurait un SocialAppTypeenregistrement pour le type "Flickr", que tous les SocialAppenregistrements indiqueraient.

Lorsque je crée une application avec ce schéma, je reçois un avertissement indiquant qu'il n'y a pas de relation inverse entre SocialAppTypeet SocialApp.

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

Ai-je besoin d'un inverse, et pourquoi?

Alex Reynolds
la source

Réponses:

117

En pratique, je n'ai subi aucune perte de données en raison de l'absence d'inverse - du moins à ma connaissance. Un rapide Google suggère que vous devriez les utiliser:

Une relation inverse ne rend pas seulement les choses plus ordonnées, elle est en fait utilisée par Core Data pour maintenir l'intégrité des données.

- Cocoa Dev Central

Vous devez généralement modéliser les relations dans les deux directions et spécifier les relations inverses de manière appropriée. Core Data utilise ces informations pour garantir la cohérence du graphe d'objets en cas de modification (voir «Manipulation des relations et de l'intégrité du graphe d'objets»). Pour une discussion de certaines des raisons pour lesquelles vous pourriez ne pas vouloir modéliser une relation dans les deux sens, et de certains des problèmes qui pourraient survenir si vous ne le faites pas, voir «Relations unidirectionnelles».

- Guide de programmation des données de base

Matthew Schinckel
la source
5
Voir la réponse de MadNik (au bas de la page au moment où j'écris ceci) pour une explication plus approfondie de ce que signifient les documents lorsqu'ils disent que les relations inverses aident à maintenir l'intégrité des données.
Mark Amery
135

La documentation Apple a un excellent exemple qui suggère une situation où vous pourriez avoir des problèmes en n'ayant pas de relation inverse. Mappons-le dans ce cas.

Supposons que vous l'avez modélisé comme suit: entrez la description de l'image ici

Notez que vous avez une relation à un appelée " type ", de SocialAppà SocialAppType. La relation n'est pas facultative et comporte une règle de suppression «refuser» .

Considérez maintenant ce qui suit:

SocialApp *socialApp;
SocialAppType *appType;
// assume entity instances correctly instantiated

[socialApp setSocialAppType:appType];
[managedObjectContext deleteObject:appType];
BOOL saved = [managedObjectContext save:&error];

Nous nous attendons à ce que cette sauvegarde de contexte échoue car nous avons défini la règle de suppression sur Refuser tandis que la relation n'est pas facultative.

Mais ici, la sauvegarde réussit.

La raison en est que nous n'avons pas établi de relation inverse . Pour cette raison, l'instance socialApp n'est pas marquée comme modifiée lorsque appType est supprimé. Aucune validation n'a donc lieu pour socialApp avant l'enregistrement (cela suppose qu'aucune validation n'est nécessaire car aucun changement n'est intervenu). Mais en fait, un changement s'est produit. Mais cela ne se reflète pas.

Si nous rappelons appType par

SocialAppType *appType = [socialApp socialAppType];

appType est nul.

Bizarre, non? Nous obtenons nul pour un attribut non optionnel?

Vous n'avez donc aucun problème si vous avez mis en place la relation inverse. Sinon, vous devez forcer la validation en écrivant le code comme suit.

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];
MadNik
la source
9
C'est une excellente réponse; merci d'avoir expliqué en détail ce qui est - à partir de la documentation et de tout ce que j'ai lu - l'argument principal en faveur d'avoir des inverses pour tout, même lorsque la relation inverse n'a pas de signification humaine. Cela devrait vraiment être la réponse acceptée. Tout ce fil de question manque maintenant, c'est une exploration plus détaillée des raisons pour lesquelles vous pourriez choisir de ne pas avoir d'inverses, évoquées dans la réponse de Rose Perrone (et aussi dans la documentation, un peu).
Mark Amery
46

Je vais paraphraser la réponse définitive que j'ai trouvée dans More iPhone 3 Development par Dave Mark et Jeff LeMarche.

Apple vous recommande généralement de toujours créer et spécifier l'inverse, même si vous n'utilisez pas la relation inverse dans votre application. Pour cette raison, il vous avertit lorsque vous ne fournissez pas d'inverse.

Les relations ne sont pas obligées d'avoir un inverse, car il existe quelques scénarios dans lesquels la relation inverse pourrait nuire aux performances. Par exemple, supposons que la relation inverse contienne un très grand nombre d'objets. La suppression de l'inverse nécessite une itération sur l'ensemble qui représente l'inverse, ce qui affaiblit les performances.

Mais à moins que vous n'ayez une raison spécifique de ne pas le faire, modélisez l'inverse . Il aide Core Data à garantir l'intégrité des données. Si vous rencontrez des problèmes de performances, il est relativement facile de supprimer la relation inverse plus tard.

Rose Perrone
la source
24

La meilleure question est: "y a-t-il une raison de ne pas avoir d'inverse"? Core Data est vraiment un cadre de gestion de graphes d'objets, pas un cadre de persistance. En d'autres termes, son travail est de gérer les relations entre les objets dans le graphe d'objets. Les relations inverses rendent cela beaucoup plus facile. Pour cette raison, Core Data attend des relations inverses et est écrit pour ce cas d'utilisation. Sans eux, vous devrez gérer vous-même la cohérence du graphe d'objets. En particulier, les relations à plusieurs sans relation inverse sont très susceptibles d'être corrompues par les données de base, à moins que vous ne travailliez très dur pour que les choses fonctionnent. Le coût en termes de taille de disque pour les relations inverses est vraiment insignifiant par rapport à l'avantage qu'il vous en rapporte.

Barry Wark
la source
20

Il existe au moins un scénario dans lequel un bon cas peut être fait pour une relation de données de base sans inverse: lorsqu'il existe déjà une autre relation de données de base entre les deux objets, qui gérera la maintenance du graphe d'objets.

Par exemple, un livre contient plusieurs pages, tandis qu'une page est dans un livre. Il s'agit d'une relation plusieurs à un bidirectionnelle. La suppression d'une page annule simplement la relation, tandis que la suppression d'un livre supprimera également la page.

Cependant, vous pouvez également souhaiter suivre la page en cours de lecture pour chaque livre. Cela peut être fait avec une propriété "currentPage" sur Page , mais vous avez besoin d'une autre logique pour vous assurer qu'une seule page du livre est marquée comme la page actuelle à tout moment. Au lieu de cela, établir une relation currentPage entre Book et une seule page garantira qu'il n'y aura toujours qu'une seule page en cours marquée, et en outre, cette page sera facilement accessible avec une référence au livre avec simplement book.currentPage.

Présentation graphique de la relation des données de base entre les livres et les pages

Quelle serait la relation réciproque dans ce cas? Quelque chose de largement absurde. «myBook» ou similaire pourrait être rajouté dans l'autre sens, mais il ne contient que les informations déjà contenues dans la relation «livre» pour la page, et crée ainsi ses propres risques. Peut-être qu'à l'avenir, la façon dont vous utilisez l'une de ces relations sera modifiée, ce qui entraînera des changements dans la configuration de vos données de base. Si page.myBook a été utilisé dans certains endroits où page.book aurait dû être utilisé dans le code, il peut y avoir des problèmes. Une autre façon d'éviter cela de manière proactive serait également de ne pas exposer myBook dans la sous-classe NSManagedObject utilisée pour accéder à la page. Cependant, on peut soutenir qu'il est plus simple de ne pas modéliser l'inverse en premier lieu.

Dans l'exemple décrit, la règle de suppression pour la relation currentPage doit être définie sur "Aucune action" ou "Cascade", car il n'y a pas de relation réciproque avec "Nullify". (Cascade implique que vous extrayez chaque page du livre au fur et à mesure que vous le lisez, mais cela peut être vrai si vous avez particulièrement froid et avez besoin de carburant.)

Lorsqu'il peut être démontré que l'intégrité du graphe d'objets n'est pas menacée, comme dans cet exemple, et que la complexité et la maintenabilité du code sont améliorées, on peut affirmer qu'une relation sans inverse peut être la bonne décision.

Duncan Babbage
la source
Merci Duncan - c'est extrêmement proche d'un cas d'utilisation que j'ai. Essentiellement, j'ai besoin de pouvoir obtenir la dernière page d'un livre. J'ai besoin que ce soit une valeur persistante afin de pouvoir l'utiliser dans un prédicat de récupération. J'avais mis en place un "bookIAmLastPageOf" inverse, mais c'est assez absurde et cause d'autres problèmes (je ne comprends pas bien). Savez-vous s'il existe un moyen de désactiver l'avertissement pour un seul cas?
Benjohn
Existe-t-il un moyen de désactiver les avertissements? J'ai maintenant 2: ... devrait avoir une règle de suppression inverse et sans action ... peut entraîner des relations avec des objets supprimés . Et peut currentPageêtre marqué comme Optional?
SwiftArchitect
Le stockage de currentPage en tant que NSManagedObjectID au lieu d'une relation serait-il une approche valide?
user965972
4

Bien que la documentation ne semble pas exiger d'inverse, je viens de résoudre un scénario qui entraînait en fait une "perte de données" en n'ayant pas d'inverse. J'ai un objet de rapport qui a une relation à plusieurs sur les objets à signaler. Sans la relation inverse, toute modification de la relation à plusieurs a été perdue lors du redémarrage. Après avoir inspecté le débogage des données de base, il était évident que même si j'enregistrais l'objet de rapport, les mises à jour du graphique d'objet (relations) n'étaient jamais effectuées. J'ai ajouté un inverse, même si je ne l'utilise pas, et voilà, ça marche. Donc, cela peut ne pas dire que c'est nécessaire, mais des relations sans inverses peuvent certainement avoir des effets secondaires étranges.

Greg
la source
0

Les inverses sont également utilisés pour Object Integrity(pour d'autres raisons, voir les autres réponses):

L'approche recommandée consiste à modéliser les relations dans les deux sens et à spécifier les relations inverses de manière appropriée. Core Data utilise ces informations pour garantir la cohérence du graphe d'objets en cas de modification

De: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/HowManagedObjectsarerelated.html#//apple_ref/doc/uid/TP40001075-CH17-SW1

Le lien fourni vous donne des idées pour lesquelles vous devriez avoir un inverseensemble. Sans cela, vous pouvez perdre des données / intégrité. En outre, la probabilité que vous accédiez à un objet nilest plus probable.

J. Doe
la source
0

Il n'y a pas besoin de relation inverse en général. Mais il y a peu de bizarreries / bogues dans les données de base où vous avez besoin d'une relation inverse. Il y a des cas où des relations / objets disparaissent, même s'il n'y a pas d'erreur lors de l'enregistrement du contexte, s'il manque une relation inverse. Vérifiez cet exemple , que j'ai créé pour illustrer les objets manquants et comment contourner le problème, tout en travaillant avec les données de base

Dhilip
la source