Chaque relation de données de base doit-elle avoir une inverse?

Disons que j’ai deux classes d’entité: SocialApp et SocialAppType

Dans SocialApp j’ai un atsortingbut: appURL et une relation: type .

Dans SocialAppType I j’ai trois atsortingbuts: baseURL , name et favicon .

La destination du type relation SocialApp est un enregistrement unique dans SocialAppType .

Par exemple, pour plusieurs comptes Flickr, il existe un certain nombre d’enregistrements SocialApp , chaque enregistrement contenant un lien vers le compte d’une personne. Il y aurait un enregistrement SocialAppType pour le type “Flickr” que tous les enregistrements SocialApp indiqueraient.

Lorsque je crée une application avec ce schéma, je reçois un avertissement indiquant qu’il n’existe aucune relation inverse entre SocialAppType et 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?

    En pratique, je n’ai pas eu de perte de données due à l’absence d’inverse – du moins à ma connaissance. Un rapide Google vous suggère de les utiliser:

    Une relation inverse ne fait pas que rendre 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 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’object si une modification est apscope (voir «Manipulation des relations et de l’intégrité des graphes d’object»). Pour plus d’informations sur certaines des raisons pour lesquelles vous pourriez ne pas vouloir modéliser une relation dans les deux sens, et sur certains problèmes susceptibles de survenir si vous ne le faites pas, reportez-vous à la section «Relations unidirectionnelles».

    – Guide de programmation de données de base

    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 cette affaire.

    Supposons que vous l’ayez modelé comme suit: entrer la description de l'image ici

    Notez que vous avez une relation un à un appelée ” type “, de SocialApp à SocialAppType . La relation est non facultative et comporte une règle de suppression “deny” .

    Considérons maintenant ce qui suit:

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

    Ce à quoi nous nous attendons, c’est d’échouer à ce contexte, car nous avons défini la règle de suppression comme Deny 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 . À cause de cela, l’instance de socialApp n’est pas marquée comme modifiée lorsque appType est supprimé. Il n’y a donc pas de validation pour socialApp avant l’enregistrement (cela suppose qu’aucune validation n’est nécessaire car aucune modification n’est survenue). Mais en réalité, 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, n’est-ce pas? Nous obtenons aucun pour un atsortingbut non optionnel?

    Vous n’avez donc aucun problème si vous avez défini 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]; 

    Je vais paraphraser la réponse définitive que j’ai trouvée dans Plus de développement iPhone 3 de Dave Mark et Jeff LeMarche.

    Apple 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 parvenez pas à fournir un inverse.

    Les relations ne doivent pas nécessairement avoir un inverse, car il existe quelques scénarios dans lesquels la relation inverse peut nuire aux performances. Par exemple, supposons que la relation inverse contient un très grand nombre d’objects. La suppression de l’inverse nécessite une itération sur l’ensemble qui représente l’inverse, les performances d’affaiblissement.

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

    La meilleure question est: “y a-t-il une raison de ne pas avoir d’inverse”? Core Data est en réalité un framework de gestion de graphes d’objects, pas un framework de persistance. En d’autres termes, son travail consiste à gérer les relations entre les objects du graphe d’object. 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 la cohérence du graphe d’object vous-même. En particulier, les relations entre plusieurs personnes sans relation inverse sont très probablement corrompues par les données de base, à moins que vous ne travailliez très fort 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 procure.

    Il existe au moins un scénario dans lequel il est possible de faire 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 objects, qui gérera la gestion du graphique d’object.

    Par exemple, un livre contient de nombreuses pages, tandis qu’une page est dans un livre. Ceci est une relation bidirectionnelle à plusieurs. La suppression d’une page annule simplement la relation, tandis que la suppression d’un livre supprime é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 la page , mais vous avez besoin d’une autre logique pour vous assurer qu’une seule page du livre est marquée comme page courante à tout moment. Au lieu de cela, créer une relation currentPage de Book à une seule page garantira qu’il n’y aura toujours qu’une seule page en cours et que cette page sera facilement accessible avec une référence au livre avec simplement book.currentPage.

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

    Quelle serait la relation réciproque dans ce cas? Quelque chose de très absurde. “myBook” ou similaire pourrait être ajouté dans l’autre sens, mais il ne contient que les informations déjà contenues dans la relation “book” de 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 est modifiée, ce qui entraîne des modifications 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 pourrait y avoir des problèmes. Une autre façon d’éviter cela de manière proactive serait de ne pas exposer myBook dans la sous-classe NSManagedObject utilisée pour accéder à la page. Cependant, on peut avancer qu’il est plus simple de ne pas modéliser l’inverse en premier lieu.

    Dans l’exemple présenté, la règle de suppression pour la relation currentPage doit être définie sur “Aucune action” ou “Cascade”, car il n’existe aucune relation réciproque avec “Nullify”. (Cascade implique que vous déchirez chaque page du livre au fur et à mesure que vous la lisez, mais cela peut être vrai si vous avez particulièrement froid et avez besoin de carburant.)

    Quand on peut démontrer que l’intégrité du graphe d’object n’est pas menacée, comme dans cet exemple, et que la complexité du code et la maintenabilité sont améliorées, on peut soutenir qu’une relation sans inverse peut être la bonne décision.

    Bien que les documents ne semblent pas nécessiter d’inverse, je viens de résoudre un scénario qui aboutit à une «perte de données» en n’ayant pas d’inverse. J’ai un object de rapport qui a une relation à plusieurs sur les objects à signaler. Sans la relation inverse, toute modification de la relation «plusieurs» a été perdue lors de la relance. Après avoir inspecté le débogage de Core Data, il était évident que même si je sauvegardais l’object de rapport, les mises à jour du graphe d’object (relations) n’étaient jamais effectuées. J’ai ajouté un inverse, même si je ne l’utilise pas, et le tour est joué, ça marche. Donc, cela ne veut peut-être pas dire que cela est nécessaire, mais les relations sans inverses peuvent avoir des effets secondaires étranges.