[RESOLU] Versioning CoreData : Un peu de mal ...

MickMick Membre
octobre 2010 modifié dans API AppKit #1
Bonjour à  tous,

J'ai une appli Core Data qui marche bien. J'utilise cette appli, et j'ai une base de données relativement bien fournie et que je DOIS pouvoir utiliser. Mon Soucis : pour faire évoluer l'appli, je dois modifier le datamodel pour faire des choses propres. Mais si je le fais violemment, je n'ai plus accès à  ma base. J'ai donc été voir du coté du coreData programming guide au chapitre Versionning. Mais franchement j'ai un peu de mal.

Est-ce que quelqu'un a déjà  pratiqué le versioning avec CoreData et pourrait me faire part de son expérience ? j'ai du mal à  démarrer en fait. Je ne sais pas par où commencer : je dois au lancement de l'appli, vérifier les metadonnées du dataModel et comparer par exemple à  une string stockée dans les preférences. Si la base est ancienne, je dois alors .. Quoi faire ? Il faut que j'initialise un moc provisoire avec l'ancienne base, et après ? comment réécrire dans la nouvelle ?

Perdu...  :'(

Réponses

  • LexxisLexxis Membre
    octobre 2010 modifié #2
    Bonjour,
    J'ai eu à  faire une migration de base core data (format SQLITE). Les changements étaient minines (ajout de certains champs, changement de nom pour certains). Pour ma part voici ce que j'ai fait (sachant de mon expérience dans ce domaine n'est pas bien énorme):

    - J'ai créer un seconde version de mon datamodel (avec les modifs de base) que j'ai ensuite positionné comme Datamodel à  utiliser par défaut.

    - Lors de l'ouverture du "persistant store", le NSDictionnary siuvant est passé en paramètre à  la méthode "addPersistentStoreWithType"

    <br />NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:<br />[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,<br />[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, <br />nil];<br />
    


    - J'ai ensuite ajouté un fichier "mapping model" qui permet de donner à  CoreData les informations nécessaire pour passer d'un modéle de données à  un autre.

    - A la première ouverture de la base de données CoreData à  su gérer "tout seul" la migration vers la nouvelle base.

    Toujours dans mon cas la base de données est relativement petite et sur iPhone. Mais je suppose que la méthode doit être similaire sur Mac OS X.

    Voila.
  • MickMick Membre
    15:21 modifié #3
    Merci Lexxis,

    Mais ta proposition concerne Leopard il me semble. (Il y a en effet des outils à  partir de X.5 pour réaliser des migrations de dataModel). Mon soucis est que mon appli doit fonctionner sur Tiger, donc exit les outils de versioning...

    J'ai commencé, tel que le préconise Apple, à  utiliser des metaData pour indiquer un N° de version. J'arrive effectivement à  récupérer la version qui a servi à  construire le "store", et à  agir en conséquence. J'en suis à  la migration => il faut que je parcours une à  une toutes mes entités, et que je les recréent dans le nouveau managedObjectContext, et il faut pouvoir mettre à  jour toutes les relations... Bref, un gros "merdier" si je puis me permettre.

    Si qq a une solution qui ne fait pas trop mal... aux doigts !
  • CéroceCéroce Membre, Modérateur
    15:21 modifié #4
    La solution que tu prévois (ouvrir les 2 MOC et copier les objets à  la mano) est la bonne sous 10.4.
    Ouais, c'est un gros guêpier, c'est bien pourquoi Apple s'est dépêchée d'ajouter les outils de migration de base dans 10.5.
    As-tu vraiment besoin d'une compatibilité avec 10.4 ? ça devient vraiment très rare comme OS.
  • MickMick Membre
    15:21 modifié #5
    Je sais, je suis un peu en retard... mais je suis prof et actuellement mes appli doivent fonctionner sur (assieds-toi) un ibook G4 !
    j'ai tenté d'installer leopard, mais au niveau performances cela n'avait rien à  voir avec tiger. (démarrages longs, réactivité bof).

    En tout cas, j'ai saisi le principe de la migration, mais la mise en oe“uvre avec une structure complexe me laisse perplexe. (voilà  que je fais des rimes maintenant). En effet, j'avais commencé par lister toutes les entités (entityDescription), de les passer en revue une à  une et de fetcher l'ensemble des managedObjects correspondant. Puis ensuite créer poour chaque un nouveau managedbject, et copier un à  un les attributs. Mais c'était sans compter les relationships ! Il faut en effet avoir instancié les objets liés pour mettre à  jour la relation n'est-ce pas ? a moins qu'il y ait une astuce avec les ID's ? (et encore, quand je crée les nouveaux objets dans le nouveau moc, ces objets se voient attribuer de nouveaux ID's qui n'ont a priori rien à  voir avec les anciens)

    Tel que le dirait un grand poète du XXIè : Now I'm lost, and I'm screaming for help....
  • MickMick Membre
    15:21 modifié #6
    Bon, je suis parti sur un truc d'une violence... Des boucles de boucles de boucles ....
    Si le mac n'explose pas, j'aurai de la chance.
    <br />NSFetchRequest *uneRequete=[[[NSFetchRequest alloc] init] autorelease];<br />				[uneRequete setEntity:[NSEntityDescription entityForName:@&quot;Niveau&quot; inManagedObjectContext:oldContext]];<br />				NSArray *lesNiveaux=[oldContext executeFetchRequest:uneRequete error:&amp;error];<br />				NSEnumerator *enumNiveau=[lesNiveaux objectEnumerator];<br />				NSManagedObject *unNiveau;<br />				NSManagedObject *unNouveauNiveau;<br />				NSSet *lesClasses;<br />				NSEnumerator *enumClasses;<br />				NSManagedObject *uneClasse;<br />				NSManagedObject *uneNouvelleClasse;<br />				NSSet *lesEleves;<br />				NSEnumerator *enumEleves;<br />				NSManagedObject *unEleve;<br />				NSManagedObject *unNouvelEleve;<br />				while (unNiveau=[enumNiveau nextObject]) {<br />					unNouveauNiveau=[NSEntityDescription insertNewObjectForEntityForName:@&quot;Niveau&quot; inManagedObjectContext:managedObjectContext];<br />					[unNouveauNiveau setValue:[unNiveau valueForKey:@&quot;nom&quot;] forKey:@&quot;nom&quot;];<br />					lesClasses=[unNiveau valueForKey:@&quot;lesClasses&quot;];<br />					enumClasses=[lesClasses objectEnumerator];<br />					while (uneClasse=[enumClasses nextObject]) {<br />						uneNouvelleClasse=[NSEntityDescription insertNewObjectForEntityForName:@&quot;Classe&quot; inManagedObjectContext:managedObjectContext];<br />						[uneNouvelleClasse setValue:[uneClasse valueForKey:@&quot;nom&quot;] forKey:@&quot;nom&quot;];<br />						[uneNouvelleClasse setValue:unNouveauNiveau forKey:@&quot;leNiveau&quot;];<br />						lesEleves=[uneClasse valueForKey:@&quot;lesEleves&quot;];<br />						enumEleves=[lesEleves objectEnumerator]<br />						while (unEleve=[enumEleves nextObject]) {<br />							unNouvelEleve=[NSEntityDescription insertNewObjectForEntityForName:@&quot;Eleve&quot; inManagedObjectContext:managedObjectContext];<br />							[unNouvelEleve setValue:[unEleve valueForKey:@&quot;nom&quot;] forKey:@&quot;nom&quot;];<br />							[unNouvelEleve setValue:[unEleve valueForKey:@&quot;prenom&quot;] forKey:@&quot;prenom&quot;];<br />							[unNouvelEleve setValue:[unEleve valueForKey:@&quot;doublant&quot;] forKey:@&quot;doublant&quot;];<br />							[unNouvelEleve setValue:uneNouvelleClasse forKey:@&quot;laClasse&quot;];<br />						}<br />					}<br />				}<br />
    

    Et ce n'est que le début...
  • CéroceCéroce Membre, Modérateur
    15:21 modifié #7
    Il y a une subtilité si les objets ne sont pas organisés sous forme d'arbre, c'est à  dire si le modèle forme une boucle quelque part: il faut faire attention à  ne pas produire de boucles infinies en suivant les relations... Pour cela, il faut tester si l'entité est déjà  dans le MOC ou pas.
    (Je précise car je suis tombé dans ce piège autrefois).
  • MickMick Membre
    15:21 modifié #8
    Bien vu.
    Je vais tenter une autre approche : parcourir les entités une à  une, et ne pas m'occuper des relations dans un premier temps, mais créer un dictionnaire de "mapping" d'ID => Les clefs sont les objectID anciens, et les valeurs sont les objectID nouveaux. Ainsi, j'ai une table de correspondance entre les anciens ID et les nouveaux.

    Du coup, je réitère une deuxième fois le parcours des entités une à  une afin de mettre à  jour les relations. Normalement tous les objets existent déjà  lors de cette deuxième passe. Cela va m'éviter de faire des boucles imbriquées les unes dans les autres (illisible et dangereux car possibilité de boucle infinie...)

    Je vous tiens au courant (pour ce que cela intéresse !)
  • AliGatorAliGator Membre, Modérateur
    octobre 2010 modifié #9
    J'ai pas tout suivi de la discussion, mais quand je vois un code avec des boucles de boucles comme ça... un design pattern genre "Visitor" ne serait pas plus approprié pour ce genre de parcours d'arbre ?!
  • ClicCoolClicCool Membre
    15:21 modifié #10
    Ca date un peu comme soucis, et je suis pas sûr que dans ton cas ça t'arrange, mais à  l'époque je m'emmerdais pas à  migrer vers un nouveau modèle.
    Ayant toujours pour habitude de coder des fonctions d'export et d'import de mes tables sous divers format (SQLlite me fait un peu peur de part sa faible lisibilité/récupérabilité en cas de crash ...) je me contentais alors d'exporter toutes mes tables une à  une indépendamment et de coder la nouvelle fonction d'import pour le nouveau modèle pour créer les MObjects et rétablir les liens (One to One ou many to One) à  partir de l'ID.
    Ca marchait impeccable même avec de grosses tables de plus de 50 000 MObjects.
  • MickMick Membre
    15:21 modifié #11
    Ci joints mon code... qui ne marche pas !! le dictionnaire mapID devrait contenir les liens entre les anciens ID et les nouveaux.
    Lors de la deuxième itération pour mettre à  jour les relationShips, j'obtiens nil pour certains ID ! Je ne vois pas comment c'est possible... Si qq voit un problème..

    <br /><br />while (uneEntityDescription=[enumEntites nextObject]) {<br />					if ([[nomDesNouvellesEntites allKeys] <br />										containsObject:[uneEntityDescription name]]) {<br />						[uneRequete setEntity:uneEntityDescription];<br />						desObjets=[oldContext executeFetchRequest:uneRequete error:&amp;error];<br />						enumObjets=[desObjets objectEnumerator];<br />						while (unObjet=[enumObjets nextObject]) {<br />							unNouvelObjet=[NSEntityDescription insertNewObjectForEntityForName:<br />										[uneEntityDescription name] inManagedObjectContext:managedObjectContext];<br />							[mapID setObject:[unNouvelObjet objectID] forKey:[unObjet objectID]];<br />							desAttributs=[uneEntityDescription attributesByName];<br />							enumAttributs=[desAttributs keyEnumerator];<br />							while (nomDUnAtttribut=[enumAttributs nextObject]) {<br />							//ICI Il faut AGIR SELON LA VERSION DE L&#39;ANCIENNE BASE : des attributs peuvent ne plus exister<br />							//Si l&#39;attribut n&#39;existe plus, il ne faut pas mettre à  jour le champ...<br />								if([[[[NSEntityDescription entityForName:[uneEntityDescription name]<br />										inManagedObjectContext:managedObjectContext] attributesByName]<br />												allKeys] containsObject:nomDUnAtttribut])<br />								[unNouvelObjet setValue:[unObjet valueForKey:nomDUnAtttribut] forKey:nomDUnAtttribut];<br />							}<br />						}<br />					}<br />				}<br />				<br />				//Tous les objets ont été créés, et le dictionnaire mapID contient une table de correspondance<br />				//entre les ID des anciens objets et ceux des nouveaux =&gt; il faut maintenant parcourir l&#39;ensemble<br />				//des entités comme précédemment et chercher les relationships afin de les mettre à  jour.<br />				<br />				NSDictionary *desRelations;<br />				NSString *nomDUneRelation;<br />				NSEnumerator *enumRelations;<br />				id destinationRelation;<br />				id newDestinationRelation;<br />				id uneID;<br />				NSEnumerator *enumDestinationsRelations;<br />				NSManagedObject *uneDestinationRelation;<br />				NSMutableSet *tempRelations;<br />				enumEntites=[lesEntites objectEnumerator];<br />				NSEntityDescription *descriptionRelationToOne;<br />				while (uneEntityDescription=[enumEntites nextObject]) {<br />					[uneRequete setEntity:uneEntityDescription];<br />					desObjets=[oldContext executeFetchRequest:uneRequete error:&amp;error];<br />					enumObjets=[desObjets objectEnumerator];<br />					while (unObjet=[enumObjets nextObject]) {<br />						desRelations=[uneEntityDescription relationshipsByName];<br />						enumRelations=[desRelations keyEnumerator];<br />						while (nomDUneRelation=[enumRelations nextObject]) {<br />							destinationRelation=[unObjet valueForKey:nomDUneRelation];<br />							//De même, il faut ici vérifier que la relation existe dans le nouveau modèle<br />							//Si ce n&#39;est pas le cas, il ne faut pas la mettre à  jour.<br />							if([[[[NSEntityDescription entityForName:[uneEntityDescription name]<br />										inManagedObjectContext:managedObjectContext] relationshipsByName]<br />												allKeys] containsObject:nomDUneRelation]) {<br />								if ([destinationRelation isKindOfClass:[NSSet class]]) {<br />									//Cas d&#39;une To-Many : Il faut créer un set<br />									enumDestinationsRelations=[destinationRelation objectEnumerator];<br />									tempRelations=[NSMutableSet set];<br />									while (uneDestinationRelation=[enumDestinationsRelations nextObject]) {<br />										[tempRelations addObject:[managedObjectContext objectWithID:[mapID objectForKey:[uneDestinationRelation objectID]]]];<br />									}<br />									newDestinationRelation=[[tempRelations copy] autorelease];<br />								}<br />								else {<br />									if ([destinationRelation objectID]!=nil) <br />										uneID=[mapID objectForKey:[destinationRelation objectID]]; //=&gt;ICI ID nil !!<br />										newDestinationRelation=[managedObjectContext objectWithID:uneID];<br />										descriptionRelationToOne=[newDestinationRelation entity];<br />								}<br />								[unObjet setValue:newDestinationRelation forKey:nomDUneRelation];<br />							}<br />						}<br />					}<br />				}<br />
    
  • MickMick Membre
    15:21 modifié #12
    Bizarre,
    Dans mon parcours des entités, lorsque la base est en sqlite, et bien il y a des entités zappées lors de la création du mapID.
    J'ai converti en xml, et refait la manip, et là  pas de problèmes !

    Va comprendre !
  • ClicCoolClicCool Membre
    15:21 modifié #13
    J'ai pas eu la possibilité de lire ton code (impossible de scroller sur une citation ou un bout de code à  partir d'un iPhone ... mais que fait l'admin ... ?)

    Mais sachant qu'un MObjectID n'est véritablement attribuée qu'au moment de sa première sauvegarde.
    Sachant qu'une base SQLLite n'est pas sauvegardée à  chaque action.
    On peut se demander s'il ne faudrait pas forcer cette sauvegarde avant de récupérer l'ID.
    Le hic, pas des moindre, c'est que dans ce cas tu devrais au moins avoir une ID transitoire (d'avant première sauvegarde) et non pas nil ... mais ça reste une piste à  explorer peut-être ?
  • AliGatorAliGator Membre, Modérateur
    15:21 modifié #14
    dans 1288127752:

    J'ai pas eu la possibilité de lire ton code (impossible de scroller sur une citation ou un bout de code à  partir d'un iPhone ... mais que fait l'admin ... ?)
    Mais qu'apprend-t-on aux pingouins de nos jours, surtout !

    Sur iPhone, et ce d'ailleurs quelle que soit l'application, si tu veux scroller dans une zone scrollable qui est elle-même dans une autre zone scrollable " par exemple scroller dans un bloc de code dans le forum, bloc qui est dans une page elle-même scrollable " il suffit de scroller non pas avec un seul doigt (ce qui fait scroller la page) mais avec deux doigts (ce qui la fera scroller la zone scrollable "du niveau inférieur")

    C'était l'astuce du soir, à  vous les studios !
  • MickMick Membre
    15:21 modifié #15
    DECEPTION !!!!  :'( Cela ne marche pas en fait. J'y ai cru, mais j'avais fait une boulette dans un numéro de version !

    Les entités "de base" sont bien présentes, mais les relations sont "corrompues". Je ne comprends pas pourquoi.
    Peut-être que c'est à  cause des ID provisoires ? j'avoue être un peu paumé.
    Je vais tenter de ne mettre à  jour QUE les to-one, et laisser core data gérer les to-many via les inverseRelationships pour voir si c'est pas ça le bug. (en effet, j'ai respecté le pattern base de donnée relationnelle, chez moi, many-to-many ça n'existe pas...)

    Je vous tiens au courant.
  • MickMick Membre
    15:21 modifié #16
    CA MARCHE !!!!!!!!

    J'avais stupidement mis à  jour les anciens objets au lieu des nouveaux ! Vraiment un boulet quand je m'y mets.

    Si des gens sont intéressé par ma méthode de mise à  jour je peux envoyer mon code.
  • ClicCoolClicCool Membre
    15:21 modifié #17
    Content que t'ais trouvé Mick

    dans 1288141087:

    dans 1288127752:

    J'ai pas eu la possibilité de lire ton code (impossible de scroller sur une citation ou un bout de code à  partir d'un iPhone ... mais que fait l'admin ... ?)
    Mais qu'apprend-t-on aux pingouins de nos jours, surtout !

    Sur iPhone, et ce d'ailleurs quelle que soit l'application, si tu veux scroller dans une zone scrollable qui est elle-même dans une autre zone scrollable " par exemple scroller dans un bloc de code dans le forum, bloc qui est dans une page elle-même scrollable " il suffit de scroller non pas avec un seul doigt (ce qui fait scroller la page) mais avec deux doigts (ce qui la fera scroller la zone scrollable "du niveau inférieur")

    C'était l'astuce du soir, à  vous les studios !


    Ohoooo, merci Ali :)

    J'ai la vague impression d'avoir déjà  fait ainsi y'a pas si longtemps mais j'avais complètement oublié jusqu'à  l'existence d'une possibilité.
Connectez-vous ou Inscrivez-vous pour répondre.