Coredata: autoincrémenter une valeur

TournesolTournesol Membre
04:52 modifié dans API AppKit #1
Bonjour à  tous,

Je tente d'apprendre à  utiliser coredata, mais j'ai déjà  un petit problème. J'ai une entité "client", qui a divers attributs, tels que nom, prénom, adresse, etc. mais également un attribut ID que j'aimerais incrémenter à  chaque ajout d'un nouveau client. Et je ne sais pas exactement comment m'y prendre, j'aurais deux pistes:
1. faire un fetch pour avoir tous les attributs ID de mon entité client dans un tableau, et ensuite trouver la valeur maximale du tableau;
2. utiliser les bindings: un utilisant interface builder j'arrive à  obtenir dans un champ texte la valeur maximale de mes ID, en configurant les bindings comme cela:
value:
Bind to: Clients (qui est un NSArrayController lié à  mon entité "client")
Controller Key: arrangedObjects
Model Key Path: @max.ID
Maintenant ma question serait: comment faire la même chose dans XCode, car je n'ai pas envie que cette valeur soit affichée dans un champ de texte, mais j'ai envie de l'utiliser dans mon code.

Voilà , j'espère que j'ai réussi à  me faire comprendre... Désolé pour ce long message. ça serait vraiment sympa si quelqu'un pouvait me donner son avis, ou une piste... Merci d'avance!

Réponses

  • TournesolTournesol Membre
    juillet 2006 modifié #2
    Bonjour,

    Alors je progresse :-) J'arrive à  déterminer la valeur du prochain ID, je procède de la façon suivante:
    Mon bouton "Nouveau client" exécute l'action "ajouterClient" dans laquelle j'arrive à  déterminer quel sera le prochain ID. Pour l'instant, elle est codée comme cela ("clients" pointe vers mon NSArrayController):
    <br />- (IBAction)ajouterClient:(id)sender<br />{	<br />	id mesClients = [clients arrangedObjects];<br />	int nouvelID = 1 + [[mesClients valueForKeyPath:@&quot;@max.id&quot;] intValue];<br />	[clients add:self];<br />}<br />
    


    L'action détermine le nouvel ID à  attribuer au client créé et elle ajoute une entrée. Par contre, je n'arrive pas à  enregistrer la valeur nouvelID...

    J'ai essayé de faire qqch comme
    [[clients selection] setValue:nouvelID forKey:@&quot;id&quot;];
    


    ça ne marche pas... quelqu'un aurait-il une idée?

    Merci!
  • Eddy58Eddy58 Membre
    juillet 2006 modifié #3
    Le mieux dans ce genre de cas est de sous-classer ton NSArrayController et de surcharger la méthode newObject, qui est appelée à  chaque nouvel ajout. :o

    -(id)newObject
    {
    id newObject;
       int nouvelID;

    newObject=[super newObject];
       nouvelID=[[self valueForKeyPath:@"@max.id"] intValue]+1;
    [newObject setObject:[NSNumber numberWithInt:nouvelID] forKey:@id];
    return newObject;
    }
  • TournesolTournesol Membre
    04:52 modifié #4
    Ok! merci beaucoup, j'avais pas pensé à  une solution comme celle-là !

    Je n'ai pas encore testé, mais ne faudrait-il pas mettre à  la ligne 7:
    <br />nouvelID=[[[self arrangedObjects] valueForKeyPath:@&quot;@max.id&quot;] intValue]+1;<br />
    

    ?

    Je vais essayer ce code! Merci!
  • TournesolTournesol Membre
    04:52 modifié #5
    J'ai réussi! Merci!

    J'ai dû écrire:
    <br />- (id)newObject<br />{<br />	id newObject;<br />	int nouvelID;<br />	<br />	newObject = [super newObject];<br />	nouvelID = [[[self arrangedObjects] valueForKeyPath:@&quot;@max.id&quot;] intValue] + 1;<br />	[newObject setValue:[NSNumber numberWithInt:nouvelID] forKey:@&quot;id&quot;];<br />	return newObject;<br />}<br />
    
  • Eddy58Eddy58 Membre
    04:52 modifié #6
    dans 1154269992:

    J'ai réussi! Merci!

    Mais de rien ! ;)

    dans 1154269992:

    J'ai dû écrire:
    <br />- (id)newObject<br />{<br />	id newObject;<br />	int nouvelID;<br />	<br />	newObject = [super newObject];<br />	nouvelID = [[[self arrangedObjects] valueForKeyPath:@&quot;@max.id&quot;] intValue] + 1;<br />	[newObject setValue:[NSNumber numberWithInt:nouvelID] forKey:@&quot;id&quot;];<br />	return newObject;<br />}<br />
    


    Ok, et sans le arrangedObjects ça fait quoi ?
  • ChachaChacha Membre
    04:52 modifié #7
    dans 1154267645:

    Le mieux dans ce genre de cas est de sous-classer ton NSArrayController et de surcharger la méthode newObject, qui est appelée à  chaque nouvel ajout.


    En fait non, Apple préconise plutôt de surcharger la méthode "awakeFromInsert" de l'objet, qui est appelée une seule fois à  sa création.
    http://developer.apple.com/documentation/Cocoa/Conceptual/NSPersistentDocumentTutorial/03_CustomClass/chapter_4_section_4.html

    +
    Chacha
  • TournesolTournesol Membre
    04:52 modifié #8
    dans 1154270251:

    Ok, et sans le arrangedObjects ça fait quoi ?


    Sans le arrangedObjects, ça compile, mais à  l'exécution ça ne fonctionne pas. Dans la fenêtre Run Log, j'ai ce message d'erreur:
    *** NSRunLoop ignoring exception '[<ArrayControllerWithIDAutoIncrement 0x36c070> valueForUndefinedKey:]: this class is not key value coding-compliant for the key @max.' that raised during posting of delayed perform with target 3aaed0 and selector 'invokeWithTarget:'

    Chacha: merci pour le lien! Je n'ai pas fait ce tutoriel, je crois que je devrais...
  • ChachaChacha Membre
    04:52 modifié #9
    dans 1154276938:

    Sans le arrangedObjects, ça compile, mais à  l'exécution ça ne fonctionne pas. Dans la fenêtre Run Log, j'ai ce message d'erreur:

    Je ne sais pas exactement quel est le problème, mais sous-classer NSArrayControlle rjuste pour ça me semble de toutes façons un peu dommage. Le "awakeFromInsert" est plus intéressant car il est fait pour ça !


    Chacha: merci pour le lien! Je n'ai pas fait ce tutoriel, je crois que je devrais...

    C'est un tutoriel intéressant, en effet ! Il demande de connaà®tre les Bindings et le KVO (key-value observing), sur lesquels Apple fournit une documentation aussi. Mais pour les Bindings, tu as l'air de connaà®tre !

    +
    Chacha
  • Eddy58Eddy58 Membre
    juillet 2006 modifié #10
    dans 1154280211:

    Je ne sais pas exactement quel est le problème, mais sous-classer NSArrayControlle rjuste pour ça me semble de toutes façons un peu dommage. Le "awakeFromInsert" est plus intéressant car il est fait pour ça !

    Ne connaissant pas du tout Core Data, j'ai aiguillé Tournesol selon mes connaissances, mais à  la vue de la doc et des exemples, il est vrai que la méthode awakeFromInsert est plus appropriée car faisant directement partie de Core Data, mais cela demande toujours un sous-classement (NSManagedObject). :o  
  • ChachaChacha Membre
    04:52 modifié #11
    dans 1154280652:

    Ne connaissant pas du tout Core Data, j'ai aiguillé Tournesol selon mes connaissances, mais à  la vue de la doc et des exemples, il est vrai que la méthode awakeFromInsert est plus appropriée car faisant directement partie de Core Data, mais cela demande toujours un sous-classement (NSManagedObject).

    Ouaip, et t'as bien fait (mieux vaut une solution qu'aucune) ! Mais comme j'ai lu la doc de CoreData y a pas longtemps, cet exemple m'était revenu. Faudrait que je pratique, par contre, parce que ça a l'air très très bien, mais forcément un peu difficile.

    +
    Chacha
  • Eddy58Eddy58 Membre
    04:52 modifié #12
    dans 1154282187:

    Faudrait que je pratique, par contre, parce que ça a l'air très très bien, mais forcément un peu difficile.

    Oui, je pense qu'il est indispensable de bien maitriser les bindings avant de s'attaquer à  Core Data, car cela rajoute encore une couche supplémentaire, et l'on peu vite se perdre dès que l'on a besoin de faire quelque chose d'un peu spécial. Ceci dit, je suis tout à  fait d'accord avec le fait que cela doit avoir ses avantages quand l'on maitrise. :)
  • TournesolTournesol Membre
    04:52 modifié #13
    dans 1154280211:

    Je ne sais pas exactement quel est le problème, mais sous-classer NSArrayControlle rjuste pour ça me semble de toutes façons un peu dommage. Le "awakeFromInsert" est plus intéressant car il est fait pour ça !


    Effectivement, je pense que je vais plutôt utiliser cette solution, mais j'ai tout de même un problème. Pour incrémenter ma valeur, je dois déterminer la valeur maximale parmi tous les "id" du NSArrayController. Comment accéder au NSArrayController qui contient tous les clients? En sous-classant NSArrayController, j'y accédais simplement avec "self", mais là  je ne vois pas trop comment m'y prendre... Il y a peut-être une possibilité d'utiliser la méthode -managedObjectContext de managedObject. Je vais faire quelques tests! Sinon je pourrais utiliser une fetched property.
  • TournesolTournesol Membre
    04:52 modifié #14
    ça fonctionne! J'ai finalement opté pour la solution de Chacha, même si l'autre marchait aussi. Mon awakeFromInsert est:

    <br />- (void)awakeFromInsert<br />{<br />	int nouvelID;<br />	id context = [self managedObjectContext];<br />	nouvelID=[[[context registeredObjects] valueForKeyPath:@&quot;@max.id&quot;] intValue] + 1;	<br />	[super awakeFromInsert];<br />	[self setValue:[NSNumber numberWithInt:nouvelID] forKey:@&quot;id&quot;];<br />}<br />
    


    et ça fonctionne!

    mais je me demande si c'est très fiable, et si ça fonctionne si j'ai une autre entité qui a aussi un attribut "id"...
  • TournesolTournesol Membre
    04:52 modifié #15
    mmh... il suffit que je rajoute une autre entité pour causer des problèmes... Des autres objets sont alors créés dans le même managedObjectContext et mon code ne fonctionne plus... Va falloir que je trouve quelque chose de plus efficace.

    J'ai essayé de faire une fetched property (nom: maxID) qui a comme expression: id == @max.id pour sélectionner l'objet avec le plus grand id. Ensuite dans awakeFromInsert, pour avoir mon nouvel id, j'écris:
    <br />nouvelID = [[self valueForKeyPath:@&quot;maxID.id&quot;] intValue] + 1;<br />
    

    Mais ça ne fonctionne pas, et je comprends pas pourquoi...
    Bon, je continue à  chercher!
  • syncsync Membre
    04:52 modifié #16
    Il y a éventuellement une autre solution...

    Créer une nouvelle entité par ex ID, dans laquelle on peut mettre tous ses id pour chaque autre entité, après à  chaque nouvelle entrée dans ton autre entité il suffit d'incrémenter d'un la propriété de ton entité ID et d'ensuite récupérer cette ID et hop on le récupère dans l'autre entité.

    Si quelqu'un est intéressé je dois avoir un bout de code pour faire comme ça

    Anthony

  • TournesolTournesol Membre
    04:52 modifié #17
    dans 1154348022:

    Il y a éventuellement une autre solution...

    Créer une nouvelle entité par ex ID, dans laquelle on peut mettre tous ses id pour chaque autre entité, après à  chaque nouvelle entrée dans ton autre entité il suffit d'incrémenter d'un la propriété de ton entité ID et d'ensuite récupérer cette ID et hop on le récupère dans l'autre entité.

    Si quelqu'un est intéressé je dois avoir un bout de code pour faire comme ça

    Anthony


    Ah j'avais pas pensé à  ce type de solution. ça devrait marcher, je préfèrerais éviter de créer encore une nouvelle entité, mais si je ne trouve pas de solution pourquoi pas. Ce qui me gêne tout de même, c'est qu'on dit habituellement qu'il faut éviter d'enregistrer dans une base de donnée une information qui peut être déduite d'autres informations enregistrées. Or ici c'est le cas, car tous les id sont enregistrés, il est donc facile en théorie de trouver le plus grand.

    J'espère encore trouver une solution un peu plus élégante, mais merci pour la proposition.
  • TournesolTournesol Membre
    04:52 modifié #18
    Alors finalement je reste à  la solution de Eddy58, elle fonctionne bien et demande pas beaucoup de code.

    C'est vrai que ça aurait été mieux de mettre l'autoincrémentation dans la méthode awakeFromInsert, mais c'est un peu compliqué. J'ai trouvé une solution, en écrivant dans ma sous-classe de NSManagedObject les deux méthodes suivantes:
    <br />- (void)awakeFromInsert<br />{<br />	[super awakeFromInsert];<br />	[self setValue:[self nouvelID]];<br />}<br /><br />- (NSNumber *)nouvelID<br />{<br />	int nouvelID;<br />	id context = [self managedObjectContext];<br />	NSEntityDescription *client = [NSEntityDescription entityForName:@&quot;Client&quot; inManagedObjectContext:context];<br />	NSFetchRequest * request = [[[NSFetchRequest alloc] init] autorelease];<br />	[request setEntity:client];<br />	NSArray *mesClients = [context executeFetchRequest:request error:nil];<br />	if([mesClients count])<br />		nouvelID = [[mesClients valueForKeyPath:@&quot;@max.id&quot;] intValue] + 1;<br />	else<br />		nouvelID = 1;<br />	return [NSNumber numberWithInt:nouvelID];<br />}<br />
    


    Mais il y a un petit bug: les clients que j'ajoute apparaissent à  double dans ma table qui contient et affiche tous les clients. Pourtant l'enregistrement se fait correctement, et quitter puis relancer le programme suffit à  corriger l'affichage. Mais je ne sais pas comment corriger ça, et finalement je trouve que la solution de sous-classer NSArrayController est plutôt bien!

    Alors merci à  tous pour votre aide et vos conseils/suggestions!
Connectez-vous ou Inscrivez-vous pour répondre.