RelationShips CoreData

Bonjour tout le monde,
Malgé avoir vu un sujet au nom similaire je créé un nouveau post car il ne répondait pas à mes questions.

Je vous expose mon problème :

-J’ai un struct Cell qui fonctionne parfaitement sur CoreData.

Cette struct permet à l’utilisateur de créer des cellules pour l’ajout de sommes.
Au fur et à mesure l’utilisateur incrémente un array que j’affiche.

-J’ai une deuxième struc Budget.

Le but est que l’utilisateur créé un budget, et que pour chaque budgets différents il puisse créer un tableau de cellule différent.

Voici une image de ce à quoi ressemble une vue de la truct budget :

Le souci c’est que je n’arrive pas à pouvoir créer un nouveau budget avec un tableau vide, de remplir les tableau et ensuite de de créer un nouveau budget qui aura lui même un nouveau tableau. Je charge à chaque fois tous les objets cell sans distinction.

Sachant que chaque budgets sera lui même dans un array et que l’utilisateur pourra charger celui qu’il veut.

Voici deux images de mon Manager ainsi que de mon dataModel :

Je ne sais donc pas comment faire pour parvenir à mes fins, faut il modifier mes structs et ma création de budget, ou bien dois-je créer des relations dans CoreData (si c’est le cas je ne sais pas comment faire, je devrais donc faire des recherches :sweat_smile:)

Si quelqu’un aurait la solution cela m’aiderai beaucoup.
Merci à vous.

Excuse-moi, je suis parfois un peu bourrin, mais j’ai du mal, pourtant tu as donné beaucoup de code, la structure réelle de tes données ; tes cellules, c’est une analogie avec une feuille de calcul ? Ce ne serait pas mieux que ce soit des débits ou des crédits affectés à un budget ? Dans la struct Budget, découvert c’est quoi ? sommeEnCours c’est quoi ? je ne me rends pas compte si le découvert est une somme au-delà du budget que tu t’autorises à sortir, une facilité de caisse, ou bien si c’est une valeur décaissée au-delà du budget, il y a une notion de durée, serait-on en fait dans un emprunt ? Moi je trouve que ce serait plus facile en comprenant mieux ce que représentent les variables dans la réalité, en fait. En plus, je ne saisis pas le lien entre tes « CDCell » et tes « CDBudget », rien de l’un n’apparaissant dans l’autre dans le schéma de tes entités.
Mais rassure-toi, c’est rare que je comprenne du premier coup…

Salut, pas de soucis.
En fait il ne faut pas se prendre la tête avec les autres variables, on peut même en faire abstraction.
L’idée principale c’est que je veux faire un tableau d’objets Cell pour chaque Objets Budget.
Mais c’est au niveau de CoreData que je bug, comment réussir à enregistrer plusieurs objets par rapport à un autre et créer une dépendance entre eux.

Alors, c’est là qu’intervient « Relationship ». Pour toute entité que tu crées, tu peux établir des Relationships. Tu donnes un nom à cette relation, et tu remplis l’entité référencée par cette relation. Par exemple, le couvercle et sa marmite, et réciproquement. Et après, il faut définir si c’est une relation « to-one », par exemple, un seul couvercle par marmite, sauf si c’est un couvercle universel qui est alors « to-many ». Il faut voir si cette relation est optionnelle, si elle pointe vers plusieurs objets ou un seul, genre un Budget avec plusieurs Cell, et pour chaque Cell, un seul budget, ce qui arrive si l’un des deux objets disparaît : une Cell peut disparaître, le Budget ne disparaît pas, (delete Rule = No Action), mais s’il disparaît quand la dernière Cell qui pointe sur lui disparaît, alors delete rule = Deny. Il y aussi la possibilité d’annuler le lien entre les objets, par exemple Cell est réaffectée à une autre Budget (delete rule = Nullify) et si tu supprimes un Budget, toutes les Cell qui en dépendent sont supprimées, delete rule = Cascade. Il faut toujours définir dans le fichier modèle, les deux relation : de Budget vers Cell, et de Cell vers Budget. Quand les deux relations sont les mêmes, avec les mêmes règles d’un côté et de l’autre, il faut fixer la relation sur « inverse », sinon c’est « no-inverse » à la moindre dissymétrie, et c’est généralement le cas, d’où c’est la valeur par défaut. Après, c’est à toi, quand tu ajoutes un objet d’un côté, d’ajouter la relation particulière, genre newMarmite = « LeCreuset », newMarmite.couvercle = « couvercleRond ».
Je ne sais pas si je t’ai vraiment aidé. Je suis d’accord, c’est très compliqué en fait. Mais, ça a l’avantage de reposer sur la description des rapports de objets dans la réalité, d’où l’intérêt de bien repérer comment ils fonctionnent entre eux.

Oui je vois, je pense avoir compris le fond, maintenant je vais essayer d’appliquer pour y voir plus clair.
Ça n’a pas l’air compliqué mais encore un peu abstrait, donc je vais me pencher dessus :wink:.
Après tu as compris mon besoin qui est de créer plusieurs cellules pour un seul Budget.
En attendant merci à toi pour le temps que tu as pris à m’aider :slightly_smiling_face:.

Bonjour à tous,
De retour (rapide) après plusieurs semaine d’inactivité sur le forum.
Je ne sais pas si tu as l’intention d’accéder à ta base CoreData par un autre moyen que ton application, mais si ce n’est pas le cas et que tu ne vas y accéder qu’avec ton application, tu n’es pas obligé de gérer les liens dans CoreData, tu peux les gérer juste dans ton application.
Moi j’ai eu le même genre de besoin, et je gère les liens directement dans le code. Je gère les liens par une propriété de type UUID dans les objets.
Si on prend l’exemple d’items rangés dans des boites, on a en gros 2 possibilités:

  • soit pour chaque item tu as un idBoite qui donne l’id de la boite dans lequel il est rangé
  • soit pour chaque boite tu as un tableau d’idItem qui donne les id des items rangés dans la boite
    Tu peux aussi mixer les 2 solutions et gérer les id dans chaque sens.
    Avec ces solutions tu dois juste stocker les éléments dans CoreData avec les id bien sur, et gérer les ajouts suppressions comme il faut. Tu n’as pas besoin de créer les liens dans CoreData.
    A ta disposition si tu veux des précisions.
    A bientôt
    Pascal
1 « J'aime »

Bonjour,
Merci pour ta réponse.
Alors non je veux tout gérer en interne et ta solution était ce que je voulais faire mais sans y parvenir.
j’avais donc créé un tableau « d’item » dans ma struct « boite » (pour reprendre ton exemple).

Le problème était que je n’arrivais pas à créer la dépendance en fonction des Id justement.
J’était partis du principe de sauvegarder les « item » dans un tableau interne, et ensuite de sauvegarder ce tableau dans le tableau qui dépend de l’objet « boite » puis de sauvegarder l’objet « boite » dans CoreData.
Et c’était la que posait le problème et je ne trouvait aucune solution.

J’ai donc étudié la solution que m’a proposé @fjacquemin, et après recherches supplémentaires je pensais avoir compris du coup je me lance :
Je créé ma relationship par l’id de « boite » dans CoreData, je créé mon objet « boite » et je dis a chaque « item » de vérifier qu’il y a bien un objet « boite » et si c’est le cas de créer « l’item » en fonction de ce dernier.

Sur le papier ça avait l’air correct, mais lors du premier essai sur le simulateur c’est le drame : le simulateur plante avec comme message : Thread 1: « « CDBudget » is not a subclass of NSManagedObject. ».

A la base les « item » se sauvegardaient parfaitement dans CoreData, j’ai le même code dans le manager pour créer la « boite »:

lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Bankroll")
        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("Unable to load persistent stores: \(error)")
            }
        }
        return container
    }()
    
    
    var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }

 func addNewBudget(budget:Budget) {
        let newBudget = CDBudget(context: context)
        newBudget.nomBudgetEnCours = budget.nomBudgetEnEnCours
        newBudget.sommeEnCours = budget.sommeEnCours
        newBudget.decouvert = budget.decouvert
        newBudget.duree = budget.duree
        saveData()
    }

private func saveData() {
    if context.hasChanges {
        do {
            try context.save()
        } catch {
            print("Erreur pendant la sauvegarde CoreData : \(error.localizedDescription)")
        }
    }
}

Je ne comprends donc pas pourquoi les boites ne sont pas des sous classes de ManageObject…

Donc pour le moment on peut dire que j’en suis toujours au même stade :sweat_smile:.

Salut,

Je n’ai pas tout lu dans le détail mais quelque chose m’a sauté aux yeux. Tu sembles parler de sous classe , mais tes objets sont des struct, pas des classes d’objets. Tu as tenté de faire de l’héritage avec des struct ? parce que ce n’est pas possible. Il te faut te vraies Class au lieu de Struct.
Bon, il y a 9 chances sur 10 que je tape à côté car je n’ai pas tout lu. Mais j’ai quand même posté, on ne sait jamais :wink:

Salut,
Non du tout c’est le message d’erreur de Xcode.
Il me dit que mes objets ne sont pas de type nsManagedObject. :sweat_smile:

Ce que l’on ne voit pas, dans le code que tu postes, c’est la classe CDBudget telle que CoraData la construis pour toi.Par ailleurs je m’inscris en faux pour ce que nous dit prq, ce n’est pas toi qui gère les liens, tu les décris seulement dans ton Model.xcdatamodeld, et ensuite lorsque tu crées tes objets. Et, ensuite, CoreData les gère pour toi, et te soulage d’avoir à te creuser les méninges à sa place. Évidemment, il faut tout de même l’interroger correctement. Mais il y a quelque chose qui est bien vrai dans ce que dit Sylvain, qui a bien remarqué que Budget, dans ton code c’est de type struct et pas de type class et une struct, ça ne peut pas être de type NSManagedObject, car, pour ça, il faut que ce soit nécessairement une classe. Si tu as particulièrement besoin d’une struct, tu peux en créer une qui s’initialise à partir des membres d’un objet classe. Mais les NSMaangedObject sont des classes. Si tu veux voir les fichiers Swift que CoreData a crée pour toi pour enregistrer les définitions en Swift de ta classe CDBudget, rechercher avec commande-F dans les fichiers .swift créés le jour de la création de ton fichier Model.xcdatamodeld, et tu verras ce qu’il a défini comme classe exactement.

Alors la class CDBudget je n’y touche pas :

**import** Foundation

**import** CoreData

**@objc(CDBudget)**

**public** **class** CDBudget: NSManagedObject {

}

Je passe par une class Manager comme dans le cours de Maxime:

func addNewBudget(budget:Budget) {
        let newBudget = CDBudget(context: context)
        newBudget.id = budget.id
        newBudget.nomBudgetEnCours = budget.nomBudgetEnEnCours
        newBudget.sommeEnCours = budget.sommeEnCours
        newBudget.decouvert = budget.decouvert
        newBudget.duree = budget.duree
        saveData()
    }

Pour les liens avec CoreData je pense avoir compris.
Le soucis actuel c’est que lorsque j’essaye d’ajouter un Objet CDBudget au contact l’application crash avec une erreur dans le logiciel qui me dit qu’il y a doublon :

**objc[50018]: Class CDBudget is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/CoreDuet.framework/CoreDuet (0x7fff86e12008) and /Users/remy/Library/Developer/CoreSimulator/Devices/212E3DA6-8315-4820-BA31-12FAF38E43A6/data/Containers/Bundle/Application/61F5D511-7C36-4025-9A52-925710992E17/Bankroll.app/Bankroll (0x10655dc20). One of the two will be used. Which one is undefined.**

Et apparement un problème avec le container, et une nullité probable (alors que ce n’est pas le cas a part si je suis passe a cote de quelque chose) :

**[error] error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'CDBudget' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?**

**CoreData: error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'CDBudget' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?**

**2021-04-05 18:40:24.706983+0200 Bankroll[50018:4556432] [error] error: +[CDBudget entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass**

**CoreData: error: +[CDBudget entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass**

**2021-04-05 18:40:24.707204+0200 Bankroll[50018:4556432] [error] error: Class 'nil' for entity 'CDBudget' is not a subclass of NSManagedObject**

**CoreData: error: Class 'nil' for entity 'CDBudget' is not a subclass of NSManagedObject**

J’ai tenté de modifier le nom CDBudget en autre chose, dans ce cas cela ne fonctionne toujours pas mais je n’ai plus le message d’implémentation en double du haut, juste celui du bas.

Il t’explique que ta classe CDBudget est définie en double ; tu as du la dupliquer sans le faire exprès. Il ne sait plus à laquelle il doit se référer. Ne change pas le nom, retrouve les deux endroits où cette classe est définie, supprime celle qui ne va pas. Tu devrais soit garder celle générée par CoreData au moment de la constitution du modèle, soit, plutôt que définir une classe qui l’est déjà, créer une autre classe, à l’usage d’un gestionnaire, par exemple, et qui va chercher la classe définie par CoreData pour la recopier, ou générer une extension à la classe CDBudget créée par CoreData, d’où l’intérêt de la retrouver, il t’indique où : mais ce n’est guère accessible. L’un est l’emplacement de la classe définie par CoreData, l’autre, c’est dans to app, qui doit s’appeler Bankroll. Celle-là doit pouvoir se trouver dans ton code. L’une des deux est de trop, tu devrais laisser CoreData gérer cela.
CoreData, ça marche bien tant qu’on ne cherche pas à faire de la chirurgie dedans. Sinon, quand même, Realm, c’est pas mal.

Oui c’est ce que j’avais compris, je vais essayer de trouver le doublon même si je ne vois pas où il est…
Sinon je modifierai ma sauvegarde.
En attendant merci a toi pour les réponses.

En tous cas, les fichiers créés par CoreData sont à chercher dans Users/Remy/Library/Developer/Xcode/DerivedData/Bankroll-pleindesignes/Build/Intermediates.noindex/Bankroll.build/Debug-iphoneos/Bankroll.build/DerivedSources/CoreDataGenerated/Model/ et comprennent 3 fichiers swift dont un est très instructif. Si tu te sens d’attaque, tu les recopies ailleurs, tu les effaces, et dans le fichier modèle de Xcode, dans l’inspecteur, tu annules la génération de ta classe, pour le faire toi-même avec les fichiers que tu as piqué au système. Inutile de dire que tant que tu ne les bricoles pas, tu es le roi du pétrole. Dès que tu les modifies, tu peux t’attendre au pire, mais parfois, tu peux rajouter des fonctions en extension et tu deviens fier de t’être donné du mal. Mais à quel prix ! À toi de voir.

Je me doute, je m’aperçois que CoreData est bien pratique mais assez capricieux apparement, mais bon je vais aller voir ca de plus près :wink:
Merci à toi.

Bonsoir,

Mon avis / expérience:

  • tu recrées un nouveau projet XCode, en activant Core Data
  • tu recrées dans ce nouveau projet tes objets Core Data, en utilisant l’outil / la fenêtre de XCode prévue à cet effet
  • tu rapatries tes autres fichiers un par un
  • tu ne modifies surtout pas les fichiers créés automatiquement par XCode pour tes objets Core Data, mais si besoin tu crées des fichiers avec des extensions

Bon courage
Nicolas

Oui, mais : ce n’est pas un fichier qu’il aurait dédoublé, mais une classe qu’il aurait redéfinie, ton truc permet de supprimer un fichier qu’il aurait recréé une seconde fois, là, il n’est pas dit que ce soit le cas. Il faut d’abord qu’il cherche s’il n’a pas redéfini quelque part à la main la classe CDBudget, ce qui expliquerait qu’elle cesserait d’hériter de NSManagedObject, avant d’en conclure qu’il a en fait deux fichiers .xcdatamodeld, auquel cas ton truc est la planche de salut certaine.
:grimacing: J’en sais quelque chose pour en être passé par là naguère…
Mais je sais aussi que lorsqu’on ne voit pas les fichiers créés par CoreData, on peut avoir tendance à croire devoir les créer à la main. Si, si. C’est fou comme il y en a qui doivent en passer par des tas de bêtises, comme moi, pour apprendre.
Tiens, par exemple, je suis sûr qu’il y en a pleins qui n’ont jamais fait sudo rm -rf / dans le terminal, pour voir. Eh bien, moi, je connais le résultat. Je ne te le dirai pas : je suis sûr que tu vois venir, et surtout, même une sauvegarde préalable n’arrange pas tout, évitez tous ce suicide mode d’emploi, les deux ou trois qui seraient tentés.

Bonsoir,
@ristretto pour le coup j’ai déjà essayé cette solution, j’ai créé un nouveau projet, déplacé les fichiers à la main, recréé un modèle CoreData en changeant également le nom, mais le problème est toujours la.

@fjacquemin la recherche de redéfinition de class est toujours en cours, je suis dessus, sans résultats pour le moment mais je cherche :sweat_smile:

Il suffit pour ça que tu aies quelque part une déclaration faite par toi de CDBudget, tandis que CoreData aurait défini pour toi CDBudget avec l’option « class generation » dans l’inspecteur. Tu ne trouverais qu’une seule définition parce que l’autre, la seconde, serait dans les fichiers générés par CoreData qui ne sont pas directement accessibles dans les fichier du projet, c’est pourquoi je t’ai indiqué comment les trouver, car sinon, tu ne peux voir qu’une seule définition que tu vas avoir donnée toi-même et tu ne vois pas la seconde., mais pourtant, il y en a alors deux, et Xcode ne sait pas à laquelle se fier…