[Résolu] CoreData : Max 100 attributes
Bonjour,
ma base de données (entity coreData) contient d'avantage que 100 champs (attributs). Je ne parle pas de combien j'ai d'enregistrements, puisque cela se fera par l'utilisateur. Non je parle bien de "fields" dans le sens d'une bd mySql par ex.
Et, donc j'ai ce message d'erreur (panneau jaune) :
xcdatamodel: Patients has more than 100 properties; consider a more shallow entity hierarchy or denormalized properties
Le message parle de properties alors qu'il s'agit de attributes (ça s'appelle comme ça dans le xcdatamodel)
J'ai cherché partout, et j'ai l'impression que je peux rester comme ça (j'aurai en gros moins de 300 attributes). Tout ce que j'ai vu, c'est au sujet des relationshpis dont je ne me sers pas pour l'instant...
C'est bizarre que l'on soit limité en nombre de champs.
Merci si quelqu'un a une idée...
Réponses
300 propriétés par entité ?
Je serai toi je reverrai un peu ma conception...
Tu sais nous monter ça ?
C'est un warning apparemment d'après mes recherches.
Maintenant, il serait peut-être plus intéressant de "diviser" tes modèles...
Ben oui.
Une application avec en gros 40 écrans avec chacun entre 7 et 10 variables à stocker dans une bdd.
Rien d'exceptionnel.
Voir un exemple ancien mais explicatif ici
Il faut stocker les positions des poignées, les contenus des champs (à droite), les valeurs de zoom et de position de l'image, etc.
Tout ça sur 40 écran. Logique non !
Ouais, j'avais vu ça. Mais est-ce que je dois modifier cela ?
Parce que c'est quand même fort de café d'être limité en nbre de champs...
Je me dis que je n'ai que cette solution. Dommage.
Non.
Pour retrouver une data dans un objet qui possède 300 attributs, c'est un peu chiant.
J'ai vu vite fait la vidéo, mais si déjà tu charges les données de la photo de profil qui est liée à la personne, c'est déjà beaucoup moins lourd comme objet que de charger la personne et toutes ses photos et données qui vont avec. Chaque photo peut avoir son zoom, son offset, etc.
Si tu te sert de Core Data "uniquement" pour faire le stockage des paramètres peut être pourrais tu rassembler tous ces paramètres au seins d'un simple NSMutableDictionary que tu peux sauver dans CoreData sous la forme d'un 'Binard Data'. Du coup tu auras beaucoup moins d'éléments à sauvegarder.
Je m'associe à Larme dans l'idée de morceler les données.
Pour aller plus loin si tu a 40 écrans tu as déjà 40 sous-ensembles de données. A toi de voir comment sont articulées les données entre elles pour optimiser le tout.
La c'est un peu comme mettre toutes tes courses de la semaines dans le même emballage...
Tant pour la lisibilité de ton modèle d'un côté que pour les performances (car si tu charges un objet CoreData avec 100 attributs alors que pour ton écran tu n'en utilises que 4 mes 96 autres seront quand même fetchés dans la base pour rien en plus !!)
Imagine, même hors CoreData, que tu veux d'écrire un octogone, défini par 8 coordonnées x et y et une couleur pour chaque sommet. Toi d'après ce que j'ai compris t'es un peu parti pour créer dans ton modèle (que ce soit une entité CoreData ou une classe NSObject classique) à définir 24 propriétés, que sont x1, y1, color1, x2, y2, color2, etc...!! Voire bien pire j'ai peur que tu sois même dans l'optique de définir 48 propriétés a ton objet, x1,y1,r1,g1,b1,x2,y2,r2,g2,b2,...
Non seulement ça devient vite volumineux et mal organisé mais en plus tu ne représentes pas tes points de manière individuelle.
Il est plus logique de dire qu'un octogone est composé de 8 points, et qu'un point est composé d'un x, d'un y, et d'une couleur, et qu'une couleur est composée d'une valeur rouge, vert et bleu. Tu décomposes ainsi ton modèle en objets plus petit, et ton octogone n'a plus que 8 propriétés, sommet1, sommet2, ... sommet8 (bon idéalement il a plutôt une propriété de type NSArray contenant un tableau de points, mais bon)
Non seulement ça découpe ton modèle, le rend plus lisible, allège la récupération en base, mais en plus comme ça tu peux aussi facilement faire des méthodes qui travaillent sur les sous-éléments de ton modèle comme une méthode qui prend un objet Point en paramètre.
Avoir un objet modèle qui a plus de 30 propriétés / attributs CoreData, c'est se tirer une balle. Et encore je dis 30 mais je dépasse rarement les 10-15. Au delà y'a toujours moyen de découper.
Merci pour toutes vos remarques.
Je vais donc couper mon entity en 6 entity par ex.
Je m'entraine d'abord sur mon entity de base, et seulement sur un attribut.
Cela fonctionnait lorsque l'attribut était rempli. (un string)
Mais sur un nouvel enregistrement (donc l(attribut vide) j'ai un problème avec toujours ce satané nil, optionnel, etc.
Donc, quand je tape sur le tableview, pour ne pas stocker mes 300 variables, je stocke le indexPath.row.
Ainsi, dans chacun de mes écran, je peux faire cela.
J'ai bien en sortie :
ou bien :
si j'avais enregistré le champ auparavant.
Mais dans le cas ou il est vide, l'attribut est donc à nil (normal) (e2L1LigneSourire = nil;)
Et si j'essaie de faire cela,
j'ai le message "Int is not convertible to Range<Int>" , alors que ça fonctionne bien si l'attribut = "toto" par ex, mais si il est nil, je ne peux pas tester s'il est nil ou pas nil : c'est bizarre ce truc. Y'a un truc qui m'échappe. Je croyais avoir bien compris le test du nil.
J'ai essayé aussi avec
ou
ou encore tout un tas de chose.
J'ai la plupart du temps l'erreur à la compilation.
Et quand ça passe, ça plante quand je vais sur l'écran. Sans message d'erreur.
Voilà , j'ai cherché partout sur le web, et je ne trouve rien qui m'aide.
Une idée ?
Bon, en fait, on oublie tout ça. Le plan de stocker l'index ça marche pas. Et c'est logique je ne m'éttend pas.
Je repose ma question autrement.
Lorsque je clique sur mon tableview, à l'aide de ça (classique)
et ça (par ex)
et évidemment les 80 lignes Apple (coreData) :
dans lesquelles sont spécifiées l'entity et le tableView (classique)
je récupère bien mes variables.
Jusque là , tout va bien.
Maintenant, si mes autres variables (celles des autres écrans) sont dans une autre entity (ce que je dois faire pour avoir des petites entity), comment dois-je faire (sur un autre écran) pour récupérer les variables correspondantes au même indexPath ?
J'ai essayé des centaines de trucs, vu plein de autos, et je ne comprend vraiment pas.
Ok à partir du tableview, on extrait de l'entity à partir de l'indexPath, mais quand on n'est plus dans un tableView, comment extraire, puisque l'on n'a pas l'index dans le coreData?
Merci d'avance
sinon il y a aussi Magical Record !!!!
C'est sympa, mais j'ai pas trop le niveau, et en plus c'est en objC/
Comment connaà®tre le numéro d'enregistrement d'une fiche dans coreData ?
Alors, on va réfléchir à comment faire.
Je n'ai pas la solution finale et la plus optimisée, et je ne sais pas comment est architecturée ton application.
Cependant, admettons tu as une sorte de clé primaire (Id d'un patient), que tu récupères quand sélectionne un patient dans la UITableView.
Tu le gardes, et quand tu arrives sur un écran, tu fais une recherche dessus.
Example:
Dans tableView:didSelectRowAtIndexPath:, tu gardes l'ID.
Dans un écran, tu fais une recherche avec le prédicat sur cet ID: SELF.idPatient == searchedId sur la table contenant les attributs que tu veux.
Dans un autre écran, pareil.
Oui, tu as vu juste, c'est ce que j'essaie de faire. Tu proposes donc (j'hésitais), que, dans chacune de mes entity, j'ai un champ id, qui sera le même dans toutes mes entity. En gros comme dans une bd classique mysql. Et à chaque écran, une recherche sur l'id et hop...
Je crois que c'est la meilleure solution, dans la mesure (chose que je ne parviens pas à admettre) où il est impossible de récupérer le numéro d'enregistrement dans la première entity (celle qui montera à partir du tableview), on n'a pas le choix : on se le crée dans chacune des entity (des tables - au sens mysql).
Je faisais également des essais avec les relationships, mais j'ai un peu du mal. à‰galement avec les entity child/parent...
Merci pour tes remarques judicieuses.
Ah non, au temps pour moi, je me suis planté dans ce que je voulais dire.
Je voulais au départ écrire: SELF.patient.idPatient
Lié l'objet de la page à un patient, qui lui a un ID. Sinon, tu vas répéter à trop de fois inutilement un champ idPatient...
Les relationships c'est fait pour ça même les one-to-one.
Par ailleurs (je répète ce que j'ai entendu), il est déconseillé d'utiliser les Parent/child relationships. Une histoire de comment cette fonctionnalité est implémentée en sql. Je trouve dingue et si quelqu'un sur ce forum me disait le contraire (que, non, c'est OK de les utiliser) je serai bien content de l'entendre. Perso, j'utilise cette fonctionnalité (j'ai sû trop tard que c'etait déconseillé).
Bon, je me suis créé un nouveau projet pour jouer avec coreData et les relationShips.
Je me suis refait les vidéos et les tutos sur le sujet. Chaud.
J'avance bien, mais...
J'ai mis en ligne le projet, ce sera plus simple.
En gros :
Un tableView (ListePatients) avec nom - prénom - sexe - Et un add new patient (la première entity)
Puis si didSelectRowAtIndexPath, j'affiche la fiche du patient (FichePatient) qui contient un add pour ajouter les nouveaux champs dans le fameux nouveau entity (Etape1) - Avec le push vers le (NouvelleEtape1)
Tout marche nickel - Y'a un (println("etape1 = \(etape1)")) qui affiche bien le nouvel enregistrement dans l'entity Etape1.
Mon soucis est dans l'affichage de la fiche patient (FichePatient)
J'essaie de récupérer les champs de l'entity Patiens (ok) mais pour ceux de l'entity Etape1, je galère cher.
Encore à cause du nil.
Dans mon vrai projet, je ne vais quand même pas, lors de l'enregistrement du premier écran, enregistrer toutes les autres entity et leurs champs à "" pour ne pas avoir de nil.
Y'a un truc qui m'échappe. Dommage, car je ne suis pas loin, mais je bloque.
Merci our votre aide précieuse.
Je parviens à faire enfin un truc intéressant.
Dans FichePatient.
J'ai bien les deux entity pour chaque enregistrement.
Attention, le modèle de donnée n'est pas censé être impacté par la vue. C'est pas logique d'avoir une relation qui se nomme étape 1. ça veut rien dire d'un point de vue modèle de donnée l'étape 1.
Tu es censé organiser tes données par groupe logique du point de vue données en elle même. Et les nommer en fonction.
Je suis justement, dans mon vrai projet, en train de faire cela. Quel boulot...
Je prend mon temps, pour bien créer les bonnes entités pour chacun de mes écrans ou bien chacun de mes groupes (pageviews) d'écran.
Je n'aurai pas d'entité parente. Je dois avoir une entité en relationship avec plusieurs autres. Tout ça en one to one.
Chacune des autres entités ne sera en relation qu'avec la première
C(était juste un test.
Mais en attendant, mon projet comporte des écrans qui se nomment justement étape1, 2, etc.
Avec en plus, comme c'est dans des pageViews, y'a aussi des étape1Level1, Level2, 3, etc.
Comme j'associe une entité à un écran (ou un groupe d'écrans) , pour ne la charger que quand je suis dans cet écran (ou ce groupe), il me semble logique que je la nomme d'un nom associé littéralement aux écrans concernés, non !!!
C'est exactement ce que je fais, comme je l'explique ci-dessous.
Merci d'avoir pris le temps de se plonger là -dedans, et merci pour vos remarques, cela me réconforte.
En attendant, j'ai modifié le projet test, déposé plus haut en pièce jointe.
En fait, lorsque j'enregistre la première entité (premier écran, donc), j'enregistre également la seconde entité (étape1 dans ce cas), avec les chats à "". Comme ça c'est jamais nil - Ouf
La modif concerne le fichier "NouveauPatient" et c'est dans l'action du bouton save :
Voici le code si quelqu'un le désire.
Et là , je n'ai plus aucune erreur.
Ce que veulent te dire Yoann et AliGator c'est que justement cette similarité de logique est suspecte.
Si la structure de ton modèle de données est expliqué par la logique d'un enchaà®nement de vue c'est qu'il est vraisemblable que le pattern MVC n'est pas respecté ; plus précisément que la partie M (Modèle, c'est à dire ta structure de données) est vraisemblablement trop dépendante de la partie C (Contrôleur, l'enchaà®nement des vues).
Si nous ne nous trompons pas, la conséquence sera que ton application pourra devenir compliquée à maintenir lorsque tu voudras ajouter d'autres fonctionnalités car ton modèle de données pourrait être trop lié aux fonctionnalités actuelles de l'application.
Oui.
Il faut quand même faire respirer ton code, le modéliser un minimum pour que toi comme d'autres puissent le comprendre et le modifier plus facilement.
Quand je vois le code ci-dessus par exemple, je pense que tu peux l'améliorer significativement avec des méthodes simples comme les suivantes :
1. Créer des extensions ou catégories en Objective-c de tes NSManagedObject pour y insérer le model, au moins le CRUD que tu sembles utiliser. Ainsi, tu devrais y ajouter une méthode qui ajoute, modifie, supprime et récupère des données. Tu peux fragmenter ceci en d'autres méthodes pour te permettre de récupérer plus facilement le Managed Object Context, créer une nouvelle entité etc..
2. Mettre le nom de tes entités en temps que constante pour une meilleure modularité.
3. à‰viter le Franglais.
4. Préférer l'optional binding et des messages d'erreurs explicites aux Forced unwrapping.
5. Ne pas mélanger la logique métier à celui du model, ce qui rejoint le 1. ci-dessus. Essayer de respecter une architecture MVC ou plus adapté ici MVVM.
6. à‰viter une trop forte dépendance à l'AppDelegate (D'après Céroce et d'autres ici) même si ça reste discutable pour certains projets selon moi.
Souvent, les écrans sont liés au modèle, quand même !
Exemple : un écran est lié au sourire, un autre écran est lié au nez. Cette séparation en écrans vient du modèle, qui ici sera :
Patient :
Nom
Date de naissance
Sourire
Nez
et ensuite,
Sourire :
coordonnées gauche
coordonnées droite
Nez :
Angle
Donc, je dirai surtout que c'est un problème de terminologie. Appeler une relationship etape1, c'est clairement une mauvaise idée.
Tu devrais plutôt l'appeler sourire !
Ensuite, il est possible que ta découpe de ta grosse entité en plusieurs entités ne recoupe pas exactement tes écrans.
Bonjour à tous.
Merci pour vos efforts. Je vois que je suis loin du compte, alors que je croyais être sur la bonne voie.
En gros je n'ai rien compris.
Je retiens une chose : j'ai un sérieux problème avec le pattern MVC.
Je met ici, quand même, une version vidéo démo du vrai projet.
Au vu de la démo, ça va être compliqué. Mais bon, c'est pas le plus grave.
Ben t'as raison, car j'ai quelques écrans avec environ 60 variables à stocker : par ex l'étape 5 level 5 - l'écran des diastèmes.
Je vais donc être obligé de faire plusieurs entités pour ce type d'écran. ça se complique grave.
Dommage qu'on ne puisse pas faire une énorme entité (300 attributs) avec toutes mes variables, comme ce que j'ai fait jusqu'à présent.
Tu veux dire regrouper les méthodes de CRUD dans une classe, comme elle fait dans cette vidéo (part 2) ?
Son fichier est SwiftCoreDataHelper. Elle le présente dans cette vidéo (part 1) à la position à 6 minutes 02 secondes.
Tu parles de ça ?
1-
auquel cas, faut que je mette une Majuscule.
Ou bien ça (le newEtape1)
2-
Donc initialisé avec Let et bien sur une majuscule
Je viens de me refaire ces deux liens sur les optionnals, etc.
Apple
CoursSwift
Je commence un peu à comprendre. AliGator m'avait pourtant fait un super cours.
Tu pourrais me dire à quel endroit ça cloche dans mon code ?
Je pense être assez incompétent pour tout comprendre. Mais je garde ça en mémoire.
J'avoue que je comprend pas trop, comment faire autrement. Quelle serait l'alternative à un écran = un controller = une entité ?
Merci d'avance...
Effectivement c'est sans doute surtout de ce côté qu'il faut te perfectionner
Faut pas le voir comme ça. Je dirais plutôt "Heureusement qu'on ne peut pas, car ça t'autoriserai à faire n'importe quoi et surtout un modèle crade, et surtout ça génèrerai de sérieux problèmes de performances de ta base si pour rien qu'un seul enregistrement (pour chaque instance NSManagedObject) tu forçais du coup à fetcher 300 champs dans ta base, ce qui commence a devenir coûteux.
Je t'invite aussi à lire mon article de blog "Thinking un Swift: Saving Ponies" qui parle de pourquoi c'est mal d'utiliser à tout bout de champ des "!" et comment les éviter.
Le plus adapté serait certainement :
1) Soit passer ton graphe objet (ton objet Patient en cours de création + ses objets liés) a chaque étapeVC, pour que chaque étape remplisse juste les champs qui lui sont liés. Un peu comme si tu avais à remplir un long formulaire mais que tu be remplissais que e que tu sais remplir et passait ensuite la feuille à ton comptable pour qu'il remplisse les infos que tu connaissais pas sur ton numéro de client bancaire, puis que tu la passais à ton juriste pour qu'il remplisse d'autres infos... chaque étape ca remplir juste ce qui la concerne. A la fin de toutes les étapes tu auras un objet rempli que tu pourras sauver en base CoreData seulement une fois arrivé tout au bout.
2) Ou alors avoir des objets intermédiaires (un peu comme les ViewModels du pattern MVVM), très liés à la vue et ton découpage UI, et t'en servir pour véhiculer les variables de chaque étape, et à la fin (après la dernière étape quand tout est rempli) tu construits tes NSManagedObjects (Patient & co) à partir de ces objets ViewModel et sauve dans CoreData. Ce qui est un peu comme la première solution mais avec des classes intermédiaires pour faire le pont entre UI et Modèle et que tu gardes une indépendance relative entre les 2.
3) tu crées ton entité principale (ou fetch) au début du processus. J'imagine que tous tes VC qui remplissent les infos sont chapeautés par un VC (sinon, tu mets ça dans un classe à part, par exemple une share de instance (un singleton)). Dans ce VC en chef, ou dans Le singleton créé pour ça (tu peux l'appeler MyFormAssistant) tu gardes une référence au nsmanagedobject en chef (celui qui correspond au patient). Dans MyFormAssistant, tu crées des méthodes genre function getOrCreateRelatedSmileEntity() : ()-> NSManagedObject qui font ce que tu imagines.
À chaque écran, tu appelles cette méthode (si tu la mets dans un singleton, tu peux l'appeler directement. Si c'est dans le VC parent, tu devras te trimballer une référence weak vers le parent dans chaque écran. Autant dire : le singleton semble mieux), tu remplis ce que tu veux, et tu peux sauver a chaque instant.
Sinon, un outil génial pour coredata c'est mogenerator.
Pour schématiser, c'est définir un endroit pour gérer exclusivement les actions sur une entité. Tu peux crées une extension de Patients que tu peux organiser dans un nouveau fichier dans lequel tu mets les actions : addPatient, deletePatient, findPatient, allPatients etc. C'est à toi de voir selon tes besoins. Cette méthode te permet de mieux découper ton modèle.
Pour les constantes je parlais surtout du nom de l'entité :
Patients est écris en dur or pour ma part je l'écrirais comme une constante puisque tu risques de l'utiliser de nombreuses fois. ça évite des erreurs et si demain tu veux modifier le nom de ton entité pour X raisons, tu ne le fais qu'à un seul endroit.
C'est partout où tu mets "!" tout simplement. Ou alors faut être absolument sur du cas.