web-dev-qa-db-fra.com

Les données de base ne sauvegardent pas les objets de manière persistante

Je suis nouveau dans Core Data et, en tant que tel, je ne suis pas sûr de commettre une erreur. J'ai téléchargé des données à partir d'une API REST et celle-ci enregistre avec succès la réponse JSON sur le disque. J'essaie de traiter les données et de les sauvegarder de manière persistante à l'aide de Core Data. 

NSLog(@"inserted objects: %@", [managedObjectContext insertedObjects]);
    [managedObjectContext performBlockAndWait:^{
        NSError *error = nil;
        if (![managedObjectContext save:&error]) {
            NSLog(@"Unable to save context for class %@", className);
        } else {
            NSLog(@"saved all records!");
        }
    }];

J'ai traité avec succès la JSON et l'ajouté à une NSManagedObjectContext. Dans la première ligne, cela montre que j'ai réussi à insérer 2 objets.

inserted objects: {(
    <User: 0xa259af0> (entity: User; id: 0xa259b70 <x-coredata:///User/t44BB97D0-C4B4-4BA6-BD25-13CEFDAE665F3> ; data: {
    email = "[email protected]";
    experience = "2013-07-20";
    "first_name" = Vishnu;
    id = 2;
    "job_title" = Developer;
    "last_name" = Prem;
    location = "";
    "phone_number" = "+6590091516";
    "profile_pic" = "";
    "thumbnail_profile_pic" = "";
    "user_id" = 2;
}),
    <User: 0xa25e460> (entity: User; id: 0xa25e4c0 <x-coredata:///User/t44BB97D0-C4B4-4BA6-BD25-13CEFDAE665F2> ; data: {
    email = "[email protected]";
    experience = "2013-07-20";
    "first_name" = Sanchit;
    id = 1;
    "job_title" = Developer;
    "last_name" = Bareja;
    location = "";
    "phone_number" = "+15106127328";
    "profile_pic" = "";
    "thumbnail_profile_pic" = "";
    "user_id" = 1;
})
)}

Quand j'ai essayé [managedObjectContext save:&error], il réussit si bien et affiche "comme enregistré tous les enregistrements" comme prévu. Cependant, lorsque je consulte le fichier .sqlite de mon application et que je vérifie la présence d'objets ajoutés, je réalise qu'il n'a ajouté aucun objet à la base de données. 

Lors du redémarrage de l'application, j'imprime une liste d'objets qui se trouvent déjà dans la base de données et confirme que je ne les ai pas encore enregistrés. 

Est-ce que quelqu'un sait ce qui se passe et pourquoi je ne suis pas en mesure de sauvegarder les données de manière persistante même s'il semble que j'ai créé avec succès les objets "Utilisateur" qui doivent être enregistrés dans le modèle Core Data.

MODIFIER:

voici où je crée la NSPersistentStoreCoordinator

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"RTModel.sqlite"];

    NSError *error = nil;
    NSLog(@"Test 1");
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSLog(@"Test 2");

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.


         If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.

         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]

         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.

         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _persistentStoreCoordinator;
}

J'ai 3 contextes. 

  • masterManagedObjectContext

  • backgroundManagedObjectContext

  • newManagedObjectContext

master est le parent du fond et du nouveau. Quand j'interroge les contextes comme ceci:

    NSError *error = nil;
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"User"];
    [request setSortDescriptors:[NSArray arrayWithObject:
                                 [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES]]];
    [request setReturnsObjectsAsFaults:NO];
    NSArray *testArray = [[[RTCoreDataController sharedInstance] newManagedObjectContext] executeFetchRequest:request error:&error];

    for (User *obj in testArray) {
        NSLog(@"obj.id %@", obj.id);
    }

    NSLog(@"query records: %@",testArray);

master et background renvoient le bon obj.id dans le NSLog et donnent le résultat ci-dessous pour @ "query records"

   (
    "<User: 0xa3811d0> (entity: User; id: 0xa381230 <x-coredata:///User/t92BCED2D-CD17-49CC-9EBA-DF8F52F06A002> ; data: {\n    email = \"[email protected]\";\n    experience = \"2013-07-20\";\n    \"first_name\" = Sanchit;\n    id = 1;\n    \"job_title\" = Developer;\n    \"last_name\" = Bareja;\n    location = \"\";\n    \"phone_number\" = \"+15106127328\";\n    \"profile_pic\" = \"\";\n    \"thumbnail_profile_pic\" = \"\";\n    \"user_id\" = 1;\n})",
    "<User: 0xa382170> (entity: User; id: 0xa3820b0 <x-coredata:///User/t92BCED2D-CD17-49CC-9EBA-DF8F52F06A003> ; data: {\n    email = \"[email protected]\";\n    experience = \"2013-07-20\";\n    \"first_name\" = Vishnu;\n    id = 2;\n    \"job_title\" = Developer;\n    \"last_name\" = Prem;\n    location = \"\";\n    \"phone_number\" = \"+6590091516\";\n    \"profile_pic\" = \"\";\n    \"thumbnail_profile_pic\" = \"\";\n    \"user_id\" = 2;\n})"
)

cependant "new" retourne (null) pour obj.id dans NSLog et renvoie ce qui suit pour @"query records":

(
    "<User: 0xa2b08a0> (entity: User; id: 0x95aebe0 <x-coredata:///User/tBFCC6C5F-7D2C-4AA0-BA96-B806EE360A762> ; data: <fault>)",
    "<User: 0xa2b0910> (entity: User; id: 0xa4b9780 <x-coredata:///User/tBFCC6C5F-7D2C-4AA0-BA96-B806EE360A763> ; data: <fault>)"
)
16
Sanchit Bareja

D'après votre code et les commentaires, il semble que vous n'enregistrez pas le contexte principal. Assurez-vous d'appeler

[managedObjectContext save:&error]; 

sur tous les contextes enfants qui sauvegardent les données, et après également sur le contexte maître.

12
Mundi

Je viens juste de me frapper la tête contre le même problème. UITableViewController a récupéré une sous-classe de NSManagedObject à partir de NSManagedObjectContext, vérifié si un attribut était nil et s'il avait été téléchargé, définissez cet attribut, puis sauvegardé NSManagedObjectContext. Quelque chose comme ça:

MyManagedObject *mgObject = //get object from NSFetchResultsController
NSManagedObjectContext *mgObContext = mgObject.managedObjectContext;
if (!mgObject.data)
{
    mgObject.data = [NSData dataWithContentsOfURL:urlWithData];
    [mgObContext performBlock ^{
        NSError *saveError = nil;
        BOOL saveResult = [mgObContext save:&saveError];
        if (saveError || !saveResult)
        {
            NSLog(@"Save not successful..");
        }
    }];
}
//do something with myObject.data

La fonction save donnait un retour booléen YES et saveError restait à zéro, mais si je quittais l'application et que je la relançais, lorsque mes données de base chargeaient mes sous-classes NSManagedObject, l'attribut de données était nil. pour télécharger les données à nouveau.

Je ne trouvais pas vraiment de solution à ce problème n'importe où… lire la documentation Core Data n'a pas aidé. La solution m'est venue lorsque j'ai considéré la différence entre le code ci-dessus et mon code qui définit les attributs dans les méthodes d'usine de la sous-classe NSManagedObject, qui est fondamentalement:

MyManagedObject *mgObject = [NSEntityForDescription insertNewObjectForEntityName:@"MyManagedObject" inManagedContext:mgObContext];
mgObject.attribute1 = some value
mgObject.attribute2 = another value

La seule différence est que j'appelle les méthodes d'usine à partir d'un [mgObContext performBlock:].

Le code modifié est donc:

MyManagedObject *mgObject = //get object from NSFetchResultsController
NSManagedObjectContext *mgObContext = mgObject.managedObjectContext;
if (!mgObject.data)
{
    [mgObContext performBlock: ^{
        mgObject.data = [NSData dataWithContentsOfURL:urlWithData];
        NSError *saveError = nil;
        BOOL saveResult = [mgObContext save:&saveError];
        if (saveError || !saveResult)
        {
            NSLog(@"Save not successful..");
        }
    }];
}
//do something with myObject.data

Qui, jusqu'à présent, fonctionne parfaitement. Je pense donc que chaque fois que vous apportez des modifications aux attributs de NSManagedObjects, vous devez le faire sur le thread de leur NSManagedObjectContext.

10
vaticRite

Je me suis dit que j'ajouterais aussi quelques informations aux personnes susceptibles d'avoir des problèmes similaires. 

D'après mon expérience, tenter de sauvegarder des objets pour lesquels les champs remplis ne sont pas suffisants ne semble pas persister lors de la sauvegarde et aucune erreur ne semble être générée lorsque c'est le cas. Vérifiez toujours que vos champs sont remplis comme prévu avant la sauvegarde.

Une autre façon d’examiner ces types de problèmes consiste à renverser le problème. Peut-être que l'objet a effectivement été enregistré, mais la méthode permettant de vérifier qu'il a bien été enregistré est fausse. Pour ce faire, vous pouvez souvent interroger CoreData pour le (s) enregistrement (s) en utilisant certains critères. Vérifiez à nouveau que vos critères sont corrects et que votre requête renvoie réellement ce que vous attendiez. 

S'il ne renvoie pas ce que vous attendez, cela peut être dû à vos propres erreurs, mais il se peut également que le tableau stockant vos résultats ne les stocke pas correctement. J'ai déjà rencontré des cas où je devais renommer une NSArray parce que quelque chose à propos du nom du tableau posait des problèmes de référencement et que le tableau ne pouvait donc pas pointer vers les résultats que j'attendais. À votre santé.

5
Marcel

Ajoutez ceci après avoir sauvegardé vos données:

NSError *error = nil;
if (![managedObjectContext save:&error]) {
    NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
}
2

Je sais que ce n'est pas une réponse à la question posée par le PO, mais je voulais partager mon expérience sur le même sujet au cas où cela aiderait quelqu'un d'autre. 

J'ai eu quelques problèmes avec la sauvegarde persistante des données, tout semblait m'aider à résoudre le problème. La structure était très simple, une entité avec un champ et une relation (plusieurs). J'ai apporté quelques modifications à la classe générée, NSMutableOrderedSet au lieu de NSOrderedSet

Je ne faisais pas du multi-thread, ou quelque chose comme ça, je ajoutais simplement des éléments à la relation. Après avoir enregistré et relancé l'application, les données ont tout simplement disparu (éléments ajoutés à la relation).

J'ai fini par découvrir qu'il existe une propriété appelée updated . Après avoir ajouté le nouvel élément à la relation, j'ai vérifié si cette propriété avait changé sa valeur. Ça n'a pas. Je devais donc créer un autre champ dans l'entité, un booléen, pour pouvoir forcer l'enregistrement de l'entité après l'ajout d'éléments à cette relation.

entity.addObject(..)
entity.forceUpdate = true // without this line, it won't update
managedContext.save(..)

J'espère donc que cela aidera tout le monde avec le même problème, car j'ai passé un certain temps à penser que je ne l'enregistrais pas correctement.

2

Je suis un débutant avec iOS, mais j’ai fait quelques exemples avec CoreData pour stocker les informations des utilisateurs.

Tout d'abord, vous devez créer votre modèle avec votre entité (je suppose que vous l'avez déjà fait). Dans mon exemple, mon entité s'appelle "Utilisateur".

Tout d'abord, ajoutez une propriété similaire à celle-ci

NSManagedObjectContext *context;

à votre classe ViewController.

Deuxièmement, dans votre méthode viewDidLoad, ajoutez ces deux lignes:

AppDelegate *appdelegate = [[UIApplication sharedApplication]delegate];
context = [appdelegate managedObjectContext];

Et troisièmement, stockez vos informations:

NSEntityDescription *entitydesc = [NSEntityDescription entityForName:@"User" inManagedObjectContext:context];
NSManagedObject *newUser = [[NSManagedObject alloc]initWithEntity:entitydesc insertIntoManagedObjectContext:context];
[newUser setValue:(NSString *)[dictionary objectForKey:@"name"] forKey:@"name"];
[newUser setValue:(NSString *)[dictionary objectForKey:@"surname"] forKey:@"surname"];
...

NSError *error;
[context save:&error];

(Je tire mes propriétés d'un dictionnaire appelé NSDictionary)

Pour lire vos infos:

AppDelegate *appdelegate = [[UIApplication sharedApplication]delegate];
context = [appdelegate managedObjectContext];
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:@"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];

//NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NULL"];
[request setPredicate:nil];

NSError *error;
NSArray *matchingData = [context executeFetchRequest:request error:&error];
//NSArray *matchingData = [context executeFetchRequest:nil error:&error];

// If the user is not logged in previously
if (matchingData.count <=0 ){
    //self.displaylabel.text = @"No person find";
} else {
// If the user is already logged in
    for (NSManagedObject *obj in matchingData) {
        AppDataModel *appDataModel=[AppDataModel getInstance];
        appDataModel.appUserInfo = [User alloc]; 
        appDataModel.appUserInfo.name = [obj valueForKey:@"name"];
        appDataModel.appUserInfo.surname = [obj valueForKey:@"surname"];
    }
}
2
EnriMR

Après des heures de débogage, j'ai constaté que la raison pour laquelle mes mises à jour n'étaient pas enregistrées était parce que dans ma sous-classe de NSManagedObject, j'avais été définie par les propriétés w/@synthesize au lieu de @dynamic.

Après que je le change, tout est enregistré comme prévu.

J'espère que ça a aidé quelqu'un.

0
Khon