[Résolu] Relation réflexive dans Core Data

berfisberfis Membre
mai 2013 modifié dans API AppKit #1

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

  • colas_colas_ Membre
    mars 2013 modifié #2
    Je ne vois pas d'autre possibilité que de "symétriser" la relation à  un moment de ton programme.



    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.
  • CéroceCéroce Membre, Modérateur
    mars 2013 modifié #3
    'berfis' a écrit:


    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.


    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".
  • yoannyoann Membre
    Mais heu... Le many to many ça existe en CoreData, pourquoi ça ne convient pas à  ton besoin ? Simplement dans tes méthodes d'ajout d'amis tu fait une validation de "c'est pas self".
  • berfisberfis Membre
    mars 2013 modifié #5
    Les trois solutions sont acceptées par Core Data.



    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.
  • yoannyoann Membre
    Mais pourquoi tu me parles de table ?!



    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]Mais pourquoi tu me parles de table ?[/font]




    [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éroceCéroce Membre, Modérateur
    mars 2013 modifié #8
    J'avais compris comme Yoann. Quand on dit table et colonne, moi, j'entends base de données, pas NSTableView.


    'berfis' a écrit:


    Comment assurer la réciprocité entre les deux différentes relations?


    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.
  • AliGatorAliGator Membre, Modérateur
    Hein ? Quel rapport ? Tu as l'air de confondre différents aspects du MVC.



    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
  • CéroceCéroce Membre, Modérateur
    mars 2013 modifié #10
    'Céroce' a écrit:


    J'avais compris comme Yoann. Quand on dit table et colonne, moi, j'entends base de données, pas NSTableView.


    Visiblement, AliGator aussi.

    (Désolé pour l'auto-citation).
  • A tous: navré pour la méprise. Je n'ai aucune compétence en base de données, SQL ou autre, pour moi "table", "colonnes" sont des NSView. Donc, en fait:



    [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...
  • Ces questions là  ne concernent pas le modèle de donnée mais ce que tu va en faire sur ton contrôleur en effet.



    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.
  • Entièrement d'accord, le +/- c'est d'autant plus "lisible" s'il n'y a pas de relation.



    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?
  • Non mais le truc c'est qu'il n'y a rien à  faire, c'est un cas typique de CoreData, c'est juste des relations entre objets... Où est ton problème ? Par ce qu'hormis demander comment faire tu ne dis pas ce qui te bloque.
  • Tu as raison, geindre ne fera pas avancer les choses. Alors voilà :



    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.
  • Bon je ne comprends rien à  pourquoi tu bloque. Voici un exemple très simple de comment faire.
  • Wow !

    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.
  • 'berfis' a écrit:


    Wow !

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




    Genre ça http://9gag.com/gag/6924354 ^^
  • berfisberfis Membre
    avril 2013 modifié #19

    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

    if (newFriend)[selectedPeople addFriendsObject:newFriend];


    pour empêcher le plantage, mais il ne devrait pas retourner nil... J'ai essayé de "forcer" le chargement avec:

    [[self managedObjectContext] refreshObject:newFriend mergeChanges:YES];


    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? ???
  • Effectivement je suis allez un peut vite dans la démo, si tu relance ton appli tu verra que ça marche. Le problème vient des fetchProperties qui ne sont pas KVO compliant, les ArrayController ne sont pas informé d'un changement lors de l'ajout d'objet + un certain cache qui doit poser problème.



    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.


  • ...si tu relance ton appli tu verra que ça marche.


    ... à  cause de ceci ?


     


    An instance of NSFetchRequest describes search criteria used to retrieve data from a persistent store.


    Alors c'est à  ça que servent les fetches: aller chercher sur le disque? Moi qui pensais que c'était dans tout le contexte. Dans ce cas, comment sélectionner les entités AVANT la première sauvegarde?

     

    Je cherche encore. Core Data, un sacerdoce.
  • Bon, j'ai trouvé. Je laisse tomber la fetch property et je code en dur.


     



    NSManagedObject *selectedPeople = [self.students.selectedObjects lastObject];
    NSManagedObjectContext *moc = [self managedObjectContext];
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@Student inManagedObjectContext:moc];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];
    NSPredicate *predicate = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind){
    return ![[selectedPeople valueForKey:@avoidances] containsObject:obj] && obj != selectedPeople;
    }];
    [request setPredicate:predicate];
    NSArray *array = [moc executeFetchRequest:request error:nil];
    [self.notAvoidances setContent:array];


     


    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.


  • AliGatorAliGator Membre, Modérateur
    avril 2013 modifié #24
    Sinon, juste pour te tenter, avec MagicalRecord ça aurait donné un code un peu plus court et plus lisible (du moins je trouve)
    NSManagedObject *selectedPeople = [self.students.selectedObjects lastObject];
    NSPredicate *predicate = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind){
    return ![[selectedPeople valueForKey:@avoidances] containsObject:obj] && obj != selectedPeople;
    }];
    self.notAvoidances.content = [Student findAllWithPredicate:predicate inContext:self.managedObjectContext];
  • 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/


  • KubernanKubernan Membre
    avril 2013 modifié #26


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


  • AliGatorAliGator Membre, Modérateur

    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.


  • berfisberfis Membre
    avril 2013 modifié #29

    @ 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...


  • AliGatorAliGator Membre, Modérateur


    @ 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...




    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.

Connectez-vous ou Inscrivez-vous pour répondre.