Petite question sur une relation entre deux class & Core Data

JérémyJérémy Membre
mars 2016 modifié dans Objective-C, Swift, C, C++ #1
Bonjour à  tous, je suis nouveau sur le forum et j'aurais une petite question. Je débute dans la programmation sur iOS (swift) et j'aurais besoin d'une information sur Core Data dont je ne connais pas vraiment la mécanique interne même si je connais le principe. J'ai une classe A qui ont deux propriétés de type B (class B ).

 

+
+   +
+

|       A        |   |       B        |

|
|   |
|

| var b1: B      |-->| var bs: [A]    |

| var b2: B      |-->|                |

|                |   |                |

|                |   |                |

|                |   |                |

+
+   +
+

 

Le schéma ci-dessus représente ce que j'aimerais avoir. J'aurais une liste d'objet de type A dans la classe B qui référence tous les objets de type A qui ont l'object courant B dans B1 ou B2. Est t'il possible de mettre en place cette mécanique facilement avec Core Data ?

 

Xcode me lève un warning "should have an inverse". Merci Xcode car effectivement j'aimerais avoir la réversibilité mais un seul array dans lequel j'aurais tout mes objets A qui référence l'objet courant B. Or j'ai l'impression qu'Xcode (ou Core Data) m'impose d'avoir deux listes dans B :

 

- b1s: [A] => tous les objets A pour qui B (courant) est dans b1

- b2s: [A] => tous les objets A pour qui B (courant) est dans b2

 

Ma question est de savoir si il est possible d'avoir une seule et même liste qui référence les tous les objets A qui ont mon objet courant B dans B1 ou B2 (voir les deux).

 

Merci pour vos réponses ! :-)

Mots clés:

Réponses

  • Avoir les deux listes séparées est effectivement obligatoire. Donc tu commences par avoir deux relations b1s et b2s dans B.


    (au passage, il est très vivement conseillé de donner des noms plus explicites aux relations, et à  toutes choses d'ailleurs ; je suppose que tu as fait ça juste pour simplifier l'exemple que tu nous donne).


     


    Ensuite tu peux ajouter une "fetched property" bs, et y définir une requête pour trouver toutes les entités A dont l'attribut b1 ou l'attribut b2 est égal à  $FETCH_SOURCE.


     


    Une autre solution est de définir bs comme attribut "transient", et de définir la méthode qui va reconstituer cette liste en faisant la réunion de b1s et de b2s. Mais là  il faut un peu jongler avec le type BinaryData.


     


    Encore une autre solution. Ne pas définir de propriété supplémentaire, mais ajouter une méthode qui rend un NSSet égal à  la réunion de b1s et b2s.


  • Si tu es nouveau dans le monde du développement iOS, tu ne connais peut-être pas les excellents tutoriaux du site de raywenderlich. En voici un sur les bases de l'utilisation de CoreData en Swift :


     


    https://www.raywenderlich.com/115695/getting-started-with-core-data-tutorial



  • Avoir les deux listes séparées est effectivement obligatoire. Donc tu commences par avoir deux relations b1s et b2s dans B.


    (au passage, il est très vivement conseillé de donner des noms plus explicites aux relations, et à  toutes choses d'ailleurs ; je suppose que tu as fait ça juste pour simplifier l'exemple que tu nous donne).


     


    Ensuite tu peux ajouter une "fetched property" bs, et y définir une requête pour trouver toutes les entités A dont l'attribut b1 ou l'attribut b2 est égal à  $FETCH_SOURCE.


     


    Une autre solution est de définir bs comme attribut "transient", et de définir la méthode qui va reconstituer cette liste en faisant la réunion de b1s et de b2s. Mais là  il faut un peu jongler avec le type BinaryData.


     


    Encore une autre solution. Ne pas définir de propriété supplémentaire, mais ajouter une méthode qui rend un NSSet égal à  la réunion de b1s et b2s.




     


     


    Merci jpimbert pour ta réponse. :)


    Okay j'ai bien noté ce que tu m'as dit. Dans le cadre d'une relation "one to one", je suppose que c'est la même chose ?


     


    D'ailleurs, au passage (et nous sommes un peu loin de Core Data en lui même), le "one to one" n'est il pas un contre sens ? Voir par là  un problème de conception. Ne faudrait il pas mettre le tout dans une même classe devant ce genre de relation ? Je vois pas réellement l'apport d'éclater des données intrinsèquement liées...


     




    Si tu es nouveau dans le monde du développement iOS, tu ne connais peut-être pas les excellents tutoriaux du site de raywenderlich. En voici un sur les bases de l'utilisation de CoreData en Swift :


     


    https://www.raywenderlich.com/115695/getting-started-with-core-data-tutorial




     


    Merci Draken ! Effectivement je connais ce site et je m'appuie dessus pour faire mes devs. ;)

  • CéroceCéroce Membre, Modérateur
    mars 2016 modifié #5


    D'ailleurs, au passage (et nous sommes un peu loin de Core Data en lui même), le "one to one" n'est il pas un contre sens ? Voir par là  un problème de conception. Ne faudrait il pas mettre le tout dans une même classe devant ce genre de relation ? Je vois pas réellement l'apport d'éclater des données intrinsèquement liées...




     


    Si, il y a plusieurs intérêts:


     


    La relation est optionnelle


    L'entité "enfant" n'est pas forcément toujours présente. Par exemple, si on a une entité Rectangle, elle peut avoir des relations fillStyle et borderStyle, mais il n'est pas obligé que les deux style soient actifs (un rectangle peut ne pas avoir de couleur de fond ou de contour).


     


    Performances


    Derrière une entité, il y a une table dans la BdD SQLite. C'est à  dire que quand tu lances une Fetch Request sur l'entité parente, elle te renvoie les instances de cette entité, mais ne va pas charger les entités enfant tant que tu n'y accèdes pas. Rien que cette économie justifie les relations one-to-one. Par exemple, il est souvent intéressant de mettre les images dans une entité séparée pour éviter leur chargement implicite.


     


    Sémantique


    Si on n'avait pas les relations one-to-one, cela signifierait qu'on serait obligé de mettre tous les attributs dans la même entité. ça ferait beaucoup d'attributs, avec des noms assez longs. Toujours dans l'exemple du Rectangle, on aurait des attributs fillColor et borderColor, alors qu'avec des entités FillStyle et BorderStyle, l'attribut s'appelle simplement 'color'.


     


    Maintenant, je ne te cache pas que l'approche de Core Data n'est pas très "objet". Par exemple, on peut définir une entité Color, avec de champs 'red', 'green', 'blue', mais on ne pourra pas accrocher cette entité à  plusieurs entités, à  moins d'utiliser l'héritage, puisqu'il faut nécessairement une relation inverse.


    De toute façon, les ORM sont " à  mon avis " un mauvais compromis, et j'ai personnellement décidé de ne plus utiliser Core Data pour mes prochains développements.


  • JérémyJérémy Membre
    mars 2016 modifié #6


    Maintenant, je ne te cache pas que l'approche de Core Data n'est pas très "objet". Par exemple, on peut définir une entité Color, avec de champs 'red', 'green', 'blue', mais on ne pourra pas accrocher cette entité à  plusieurs entités, à  moins d'utiliser l'héritage, puisqu'il faut nécessairement une relation inverse.




     


    Merci pour ta réponse !   


    Apple, préconise l'emploi de relations inverses. Si le développeur décide de ne pas les créer, que risque t'il concrètement ?


  • CéroceCéroce Membre, Modérateur


    Apple, préconise l'emploi de relations inverses. Si le développeur décide de ne pas les créer, que risque t'il concrètement ?




     


    J'avoue que je ne connais pas le risque, et que je le fais rien que pour virer le warning ;-)


     


    J'ai tout de même dans l'idée que si Apple nous y pousse c'est justement parce qu'on ne peut pas partager une entité enfant avec plusieurs entités parentes. Or, cette relation inverse indique clairement cette contrainte.

  • Je vois l'idée... Mais as tu déjà  essayé (malgré les warnings) de partager une entité enfant avec plusieurs entités parentes ? Si oui, as tu constaté d'éventuelles corruptions de données ? 


  • CéroceCéroce Membre, Modérateur

    J'ai déjà  essayé et ça marchait.


    Mais c'est déjà  assez difficile de faire fonctionner Core Data dans le cas nominal, je ne m'y risquerais pas sur un projet à  livrer.


  • Okay, merci pour le tuyau ! 


  • As-tu envisagé d'utiliser https://realm.ioplutôt que CoreData ?


     


    Sinon, qu'entends-tu par partager "partager une entité enfant avec plusieurs entités parentes" ? Perso, je bosse avec CoreData et j'utilise les entités parentes.


     


    Ce qui est relou avec CoreData c'est la gestion du multi-thread.


     


    Je n'ai pas essayé Realm mais on dit que c'est mieux que Core Data.


     


    À voir...


  • CéroceCéroce Membre, Modérateur
    mars 2016 modifié #12

    Sinon, qu'entends-tu par partager "partager une entité enfant avec plusieurs entités parentes" ? Perso, je bosse avec CoreData et j'utilise les entités parentes.

    Par exemple, tu ne peux pas faire ça:
     
     
    +
    
    +
    | FillStyle |
    + +
    |           |
    + +
    | color     + +
    + +          |          + +
                           + >| Color |
                                      + +
    + +                   | red   |
    | BorderStyle |                   | green |
    + +     + >| blue  |
    |             |     |             + +
    + +     |
    | color       + +
    + +
    Parce que Color exige une relation inverse.
  • J'ai un cas où j'utilise une relation sans inverse. Cela fonctionne bien avec de nombreux utilisateurs:
    Un lieu est associé à  une icône, et beaucoup de lieux peuvent avoir la même icône.
    On a rarement besoin de savoir quels sont les lieux associés à  une icône, donc je n'ai pas fait d'inverse.


    Le problème avec les ORM c'est qu'ils imposent des contraintes qui réduisent la puissance et l'intérêt de la conception objet.
  • JérémyJérémy Membre
    mars 2016 modifié #14

    Merci Colas pour ta réponse, non je ne connais pas Realm et je n'en ai jamais entendu parler, mais je regarderai. :)


     




    Par exemple, tu ne peux pas faire ça:

     

     



    + +
    | FillStyle |
    + +
    |           |
    + +
    | color     + +
    + +          |          + +
                           + >| Color |
                                      + +
    + +                   | red   |
    | BorderStyle |                   | green |
    + +     + >| blue  |
    |             |     |             + +
    + +     |
    | color       + +
    + +

    Parce que Color exige une relation inverse.

     




     


    C'est bloquant pour toi de mettre l'inverse ? J'imagine que ça ne te servira à  rien mais bon... ;)


  • Perso ce que j'ai fait pour ce problème (si j'ai bien compris), c'est que je fais c'est que je crée plusieurs inverses !!!


    (solution moche je l'admets)


     


    Dans Color, j'ai deux relations inverses :


    - styleIfFill


    - styleIfBorder


     


     


    @Céroce, Comment as-tu fait ton schéma ? ;-) Il est joli !


  • AliGatorAliGator Membre, Modérateur
    Realm c'est surtout beaucoup plus simple à  prendre en main que CoreData.

    Débuter sur CoreData sans MagicalRecord, c'est quand même costaud car il faut assimiler beaucoup de nouveaux concepts d'un coup, donc la courbe d'apprentissage est assez rude.
    Avec MagicalRecord, ça aide énormément à  lisser la courbe d'apprentissage et à  s'y mettre déjà  un peu plus facilement.
    Avec Realm, c'est limite transparent, tu crées tes objets modèle comme tu créerais un NSObject et le reste est plutôt facile à  prendre en main.
  • CéroceCéroce Membre, Modérateur
    mars 2016 modifié #17

    Perso ce que j'ai fait pour ce problème (si j'ai bien compris), c'est que je fais c'est que je crée plusieurs inverses !!!
    (solution moche je l'admets)
     
    Dans Color, j'ai deux relations inverses :
    - styleIfFill
    - styleIfBorder


    Une première solution est de créer à  chaque fois une entité séparée, FillColor et BorderColor avec les mêmes champs red, green, blue. C'est assez propre, mais il faut créer un NSManagedObject pour chaque entité. Et si par exemple, j'ai besoin d'une méthode -[(NSColor *) nsColor], je vais devoir faire du copier-coller (= de la duplication de code).

    Alors, la solution que j'employais était de créer une entité Color et deux entités FillColor et BorderColor qui héritent de Color. Je n'avais alors plus de duplication de code (tout était dans Color), même si je devais toujours créer tous les NSManagedObject.

    Je te raconte pas la gueule du schéma à  la fin.
    De toute façon, la gueule du schéma j'avais fini par m'en foutre, sachant que Xcode a un bug de merde depuis des années: quand on renomme un NSManagedObject avec le Refactoring, il te replace toutes les entités au centre du diagramme. (J'ai évidemment posté un radar, évidemment classé en "Duplicate" par Apple).
     

    @Céroce, Comment as-tu fait ton schéma ? ;-) Il est joli !

    Avec mes petits doigts sous Sublime Text. Non, je n'ai pas d'outil pour ça...


    P.S.: Je deviens de plus en plus vulgaire quand je cause d'Apple. L'année passée " entre Core Data, Scene Kit, et les bricolages d'AppKit sous Yosemite " fut une suite de contournements des bugs et erreurs de conception d'Apple. ça m'a donné fortement envie de développer là  où l'herbe est plus verte.
  • FKDEVFKDEV Membre
    mars 2016 modifié #18

    Dans le cas d'une relation optionnelle vers un objet qui a peu de chance de disparaà®tre, je pense que ne pas mettre d'inverse n'est pas dangereux.


    Le cas des couleurs est typiquement un cas où cela devrait marcher à  partir du moment où tu acceptes de ne pas effacer les couleurs.


    Et même si jamais tu devais effacer une couleur, alors ce serait à  toi d'aller déconnecter les relations manuellement avant d'effacer la couleur.


     


    En tous, c'est ce que j'ai déduit en lisant tous les posts que j'ai trouvés sur le sujet il y a quelques années.


  • @Céroce


     


    oui pareil que toi, je n'utilises plus l'éditeur en mode "graphe" mais que en mode "liste".


     


    J'avais fait un beau graphe qui a été remis à  zéro...


  • Je vais continuer dans mes questions (toujours en rapport avec les jointures) ! Dans le cas ci-dessous, si je demande une suppression en cascade, que je supprime un objet A qui contient un objet B. Si un objet C contient ce même objet B, B sera supprimé ?


     


    Autre cas, si  je supprime un objet A qui contient un objet B mais que ce dernier n'est pas contenu dans un seul objet C. Core Data le supprime ?

     


    +
    +   +
    +   +
    +

    |       A        |   |       B        |   |       C        |

    |
    |   |
    |   |
    |

    | var b: B       |-->| var as: [A]    |<--| var b: B       |

    |                |   | var bs:    |   |                |

    +
    +   +
    +   +
    +

  • Sans tester, je dirai que B est supprimé dans les deux cas.
  • JérémyJérémy Membre
    mars 2016 modifié #22

    Ah d'accord... Donc il vaut mieux gérer les delete de façon manuel dans un tel cas. 


     


    Mais ce qui me rend sceptique (sans remettre en cause ce que tu dis), c'est le fait qu'Apple "impose" un inverse. Je me disais que ça devait être utile dans la mécanique interne de Core Data. Exemple, toujours dans mon cas, que l'ORM devait faire quelque contrôle avant de dégager une donnée pour éviter des corruptions de datas...  ::)


  • Joanna CarterJoanna Carter Membre, Modérateur

    Si tu sais ce que tu fasses, tu peux éviter les avertissements sur les relationships en trouvant les build settings :


     


    MOMC_SUPPRESS_INVERSE_TRANSIENT_ERROR (Suppress momc error on transient inverse relationships)


     


    et


     


    MOMC_NO_INVERSE_RELATIONSHIP_WARNINGS  (Suppress momc warnings on missing inverse relationships)


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