[Résolu] Relation réflexive dans Core Data
Bonjour.
Je suis coincé dans Core Data (une fois n'est pas coutume) par le problème suivant:
Soit une entité "Personne" qui peut être amie avec d'autres personnes. Une personne A crée un lien d'amitié avec une personne B, ce qui entraà®ne automatiquement sa réciproque: B devient ami avec A.
Ceci est très joli, mais cela pose le problème du "contrôleur d'amitié". Comme je n'ai rien trouvé (à part le traditionnel exemple parent <<--->> enfant), je ne vois pas comment m'en sortir.
Pour l'instant, je m'en tire à l'aide à l'aide d'une seconde entité "Amitié" (et son contrôleur qui gère le NSSet de "Personne"), mais je ne vois pas comment:
- empêcher la personne d'être amie avec elle-même, ce qui est psychologiquement recommandé mais informatiquement absurde;
- créer le lien réciproque (ajouter B à la liste d'amis de A entraà®ne que A est ajouté aux amis de B.
L'un d'entre vous a-t-il été confronté à ce problème, l'a-t-il résolu et si oui comment?
Toute ma reconnaissance sera acquise à celui qui me tirera de ce mauvais pas.
Réponses
Si tu veux prendre le problème à la source, tu peux réécrire la méthode setAmi avec quelque chose du genre :
- (void) setAmi:(Personne *) A
{
[self _setAmi:A] ;// Le setter non symétrisé
[A _setAmi:self] ;
}
et
- (void) _setAmi:(Personne *)A
{
ami = A ;
}
De plus, c'est possible que s'ajouter soi-même comme ami ne pose aucun problème informatique. 'self' est une adresse mémoire et ne contient pas la valeur de ses attributs.
PS : il faut sûrement modifier le code car ami n'est pas l'attribut (c'est amis) donc plutôt via des méthodes addAmi. Je ne sais pas comment CoreData gère les MutableSet.
Il me semble que ce postulat est faux.
En gros, ce que tu veux est une relation 1-n entre Personne et Personne, qui s'appellerait "amis".
Maintenant, ton problème est que Core Data t'oblige à définir les relations dans les deux sens. Donc, tu devras définir la relation inverse par quelque chose comme "me veut comme ami".
Soit donc l'entité Personne,
attribut "nom" et relations "lienVers" et "lienPar" (céroce), référence croisée en one-to-many.
attribut "nom" et relation "personnes" (yoann), référence à elle-même en many-to-many.
Maintenant imaginons deux tables (1 colonne) côte à côte.
A gauche, les Personne, gérées par le contrôleur Personnes, classique et trivial.
A droite, les amis, gérés par un autre contrôleur, "Amis", dont le Content Set sera Personne.selection.personnes.
@céroce:
Comment assurer la réciprocité entre les deux différentes relations?
@yoann:
Comment empêcher qu'en ajoutant un ami, je crée une nouvelle personne?
Ceci dans le cas où la table de droite a des boutons +/- pour ajouter des amitiés.
Maintenant, on peut imaginer que la table de droite comporte deux colonnes. Colonne 1: la liste de personnes identique à celle de la table de gauche. Dans la colonne 2, une check box indiquant si la personne sélectionnée dans la table de gauche est ami avec celle de la table de droite. Est-ce plus logique que d'ajouter des amitiés avec +/- ?
Je teste entretemps, mais déjà merci à vous trois.
CoreData est un modèle de base objet, les ManagedObject sont les propres garants de leurs intégrité.
Tu as une classe People qui dans son implémentation as du addFriend: qui va s'occuper de vérifier si l'amis ajouté n'est pas déjà un amis ou n'est pas sois même ou n'est pas un amis d'une personne de la relation asshole...
Tu n'as pas de contrôle tiers, tu n'est pas sur une base SQL, tu es sur une base objet et les objets contiennent naturellement leurs données et leurs méthodes de modification...
[font=helvetica, arial, sans-serif]Parce qu'un modèle c'est bien joli, mais que sans Vue/Contrôleur mon utilisateur y aura difficilement [/font][font="helvetica, arial, sans-serif"]accès...[/font]
C'est Core Data qui l'assure. Si on fais pointer la propriété amis de A sur B et C, alors la propriété réciproque m'as mis en ami de B et C va pointer sur A. Ce qui m'amène à dire que je me suis trompé: la réciproque est m'ont mis en ami. En effet, il est possible que D et E aient aussi mis B en amis.
Donc, c'est une relation n-n comme indiqué par Yoann.
Par contre, je ne sais plus si la réciproque dans une relation n-n est fixée automatiquement ou s'il faudra modifier m'ont mis en ami toi-même.
Ici le Modèle c'est ton modèle CoreData. Faut pas raisonner en terme de tables, c'est un modèle objet
Les Views et ViewControllers sont là pour afficher ton modèle, donc ton modèle objet CoreData.
Faut éviter de raisonner en terme de tables SQL & co, faut faire abstraction de tout ça. CoreData stocke ses données comme il veut (peut-être dans une base SQLite, peut-être en XML, y'a plusieurs possibilités), mais ça c'est pas ton problème. Toi ce que tu dois gérer, c'est ton modèle, donc juste le modèle objet CoreData avec ses entités. Si tu raisonnes en terme de base de données, donc tables, etc, certes tu vas y retrouver des similitudes (c'est normal c'est un ORM c'est proche d'une BDD) mais va risquer de mélanger certains concepts
Visiblement, AliGator aussi.
(Désolé pour l'auto-citation).
[font=helvetica, arial, sans-serif]Maintenant, on peut imaginer que la NSTableView de droite comporte deux NSTableColumn. [/font][font=helvetica, arial, sans-serif]NSTableColumn[/font][font=helvetica, arial, sans-serif] 1: la liste de personnes identique à celle de la [/font][font=helvetica, arial, sans-serif]NSTableView[/font][font=helvetica, arial, sans-serif] de gauche. Dans la [/font][font=helvetica, arial, sans-serif]NSTableColumn[/font][font=helvetica, arial, sans-serif] 2, une NSButtonCell (check box) indiquant si la personne sélectionnée dans la [/font][font=helvetica, arial, sans-serif]NSTableView[/font][font=helvetica, arial, sans-serif] de gauche est ami avec celle de la [/font][font=helvetica, arial, sans-serif]NSTableView[/font][font=helvetica, arial, sans-serif] de droite. Est-ce plus logique que d'ajouter des amitiés avec des NSButton +/- ?[/font]
J'espère que c'est plus clair. Reste à voir comment binder tout ça...
Coté performance et ergonomie, c'est beaucoup plus efficace de faire l'ajout d'amis par +/- . Tout afficher n'est pas scalable, dès que tu aura plus de 20 personnes dans ta liste c'est le bordel à trouver.
Mais voilà , cela fait plus de deux heures que j'ai des résultats débiles, quand le programme ne se casse pas tout simplement la figure. Je commence à douter que ce soit faisable (en tout cas par moi...).
Je ne vois pas comment bêtement établir un lien réciproque entre deux entités. Avant de tenter ma chance dans la peinture sur soie ou l'élevage de lamas, j'aimerais comprendre comment faire. Mais seul, je m'essouffle... Une idée?
Dans ma fenêtre, j'ai deux NSTableView côte à côte, celle de gauche liste les personnes, j'aimerais que celle de droite liste les relations avec d'autres personnes, sous forme de PopUpMenuCells pour choisir la personne à lier, avec également un bouton + et - pour ajouter/supprimer une relation.
Entité : Person
Attribute : name
Relationship : link <<--->> link
La TableView de gauche, avec son PersonController, a les liaisons habituelles et fonctionne.
Celle de droite a son propre contrôleur, disons LinkController, pour l'instant pas encore cous-classé.
Bindings:
LinkController : content set = PersonController.selection.link.
TableView de droite, TableColumn:
content = ?
content values = ?
selected object = ?
PopupMenuCell :
content = PersonController.arrangedObjects ?
content values = PersonController.arrangedObjects.name ?
selected object = ?
J'essaie diverses combinaisons et je n'arrive à rien d'autre que des plantages.
Pour moi, c'est l'oeuf de Colomb. Effectivement, maintenant que je l'ai devant les yeux, c'est comme le problème de maths que le prof résout devant nous: ça a l'air tout simple... jusqu'au moment où c'est notre tour...
Je comprends l'astuce du PopOver qui "intercepte" la création d'une nouvelle entité (mon [NSArrayController add:] ne pouvait que créer une nouvelle entité, pas définir une relation. Et j'aime beaucoup le "fetch" qui filtre le contenu du popUp.
Un tout grand merci d'avoir pris la peine d'envoyer cet exemple, tu m'épargnes n heures d'errance par jour, où n tend vers 24...
Et un bravo admiratif!
Bernard.
Genre ça http://9gag.com/gag/6924354 ^^
Ton exemple fonctionne dans tous les cas sauf un seul. Si je crée la personne A et la personne B, lorsque je tente d'ajouter la relation A pour B c'est OK, mais si je veux ajouter B aux relations de A, le menu est vide...
Selon le fetch predicate, le menu devrait effectivement être vide si A est déjà lié à B, mais lorsque c'est la toute première relation, le menu devrait indiquer que B est disponible.
J'ai rajouté à ton code
pour empêcher le plantage, mais il ne devrait pas retourner nil... J'ai essayé de "forcer" le chargement avec:
des fois que... mais sans résultat.
Encore une fois, pour tous les autres cas, même avec 20 personnes qui se connectent les unes aux autres, ça marche.
Est-ce que tu vois pourquoi? ???
Tu va devoir remplacer la liste des non-amis par un controller custom et non le préfabriqué
Ok, Yoann, je déclare forfait. Au bout du compte, je n'ai toujours pas de liste cohérente des fetch properties dans mon contrôleur.
J'ai essayé, en plus de ce que j'ai écrit plus haut:
- de faire un [monContrôleur rearrangeObjects] avec l'espoir que ça le forcerait à actualiser les fetches;
- de faire un [monContrôleur setContent: mesFetches] parce que j'ai déja remarqué que déclarer une array comme content d'un contrôleur dans IB, puis d'ajouter des objets à l'array n'était pas reflété dans le contrôleur -- problème de KVO.
Nada: si j'ai les personnes A, B, C et D, le menu déroulant me propose:
Pour A : rien
Pour B : A
Pour C : A, B
Pour D : A, B, C
Note que c'est assez régulier pour pouvoir dire que quelque chose ne fonctionne pas -- mais quoi? Je ne vois pas. Et dériver mon contrôleur, je veux bien, mais pour lui faire faire quoi?
D'avance merci.
... à cause de ceci ?
An instance of NSFetchRequest describes search criteria used to retrieve data from a persistent store.
Bon, j'ai trouvé. Je laisse tomber la fetch property et je code en dur.
J'aimerais savoir pourquoi, chaque fois que je fais faire ce que je veux à Core Data, je me sens aussi las que Saint Georges après sa visite au dragon... Pfff... Faut que je passe au déca.
Bonjour,
Oui c'est moins de code avec Magical Record, mais je trouve pas un autre argument pour l'utiliser. D'ailleurs y a des gens qui la recommande pas "http://www.cocoanetics.com/2013/02/zarra-on-locking/
Ca n'a strictement rien à voir. Ton lien parle d'un pattern pour la sauvegarde asynchrone (et les possibilités de lock), en utilisant notamment les child context. C'est un pattern que tu peux faire avec ou sans MR (et sur lequel je me suis cassé les dents soit dit en passant).
MR ce n'est pas ça (je ne l'utilise pas moi-même mais la doc montre bien que le but n'est pas ça).
Dans les commentaires, M. Zarra ne recommande pas MR tout simplement parce que selon lui le framework core data se suffit à lui même, suffit juste de l'apprendre.
Apprendre core data n'est pas une mince affaire. Si MR peut aider à utiliser core data sans se casser la tête c'est déjà pas mal...
Oui je suis aussi de l'avis de Kubernan.
Zarra a l'air de ne pas conseiller MR parce que "mouais c'est un wrapper de plus, je préfère utiliser CoreData direct".
Perso, j'ai appris CoreData directement avec MR et bah j'ai tout de suite compris plein de trucs que j'aurai pas compris avec CoreData de base, car avec CoreData t'as tellement de classes et de lignes à écrire pour faire une simple action (comme récupérer un tableau de tous tes Students par exemple) que quand tu débutes tu te mélanges vite je trouve : y'a bcp de vocabulaire à apprendre entre les PersistentStore, PersistantStoreCoordinator, MOC, MOM, comment relier tout ça... avec MR c'est transparent (enfin tu peux les utiliser aussi si tu veux pour faire du code plus fin ou rentrer dans des cas d'usage moins courant, mais pour débuter dans CoreData avec MR t'as même pas besoin de savoir ce qu'est un PS, PSC, ou un MOM, ni même une NSEntityDescription, etc.
C'est surtout ça qui m'a plu avec MR. En plus de faire du code plus court, t'as bien moins à apprendre pour débuter en CoreData car il gère déjà pour toi en interne tout plein de trucs. Après, quand tu progresses et que tu veux faire des trucs un peu plus poussés et utiliser toi-même tes PDC ou MOM persos, c'est toujours possible, mais au moins au début t'as pas à te prendre la tête avec ces trucs ni à rajouter ça à tout le vocabulaire et les concepts CoreData à maà®triser avant de l'utiliser.
Tu pouvais le faire avec la fetchedPropertie, il te suffisait de faire toi même l'implémentation du delegate de ta vue et ne pas utiliser des NSArrayController.
@ yoann: Tu veux dire une datasource? Mais j'aime les contrôleurs au-delà du raisonnable...
@ AliGator: Je vais télécharger et essayer. J'ai l'impression que "moins de code" ce n'est pas seulement moins de lignes, mais que ça permet surtout de ne pas noyer les lignes essentielles (=les miennes) parmi celles que Core Data impose...
Autre espoir: Apple continue à bosser sur Core Data, ils reviendront peut-être avec de bonnes idées cette année à la WWDC...
Tout à fait, c'est en fait + ça qui me plait dans MR, pas s'encombrer de lignes "inutiles pour la compréhension du code" et y voir du coup plus clair dans son code CoreData.