Correct way to refresh Core Data database - ipad

My app is setup so that when it's first used, it downloads the required data from a web based xml feed.
The user also has the option to periodically refresh the data via a setting.
When they do this, I want to delete the existing database and then recreate it via the code I use for the first load.
I read that simply deleting the database is not the correct way to do this so I'm using the following to destroy the data before loading the new dataset.
- (void)resetApplicationModel {
NSURL *_storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: DBSTORE]];
NSPersistentStore *_store = [persistentStoreCoordinator persistentStoreForURL:_storeURL];
[persistentStoreCoordinator removePersistentStore:_store error:nil];
[[NSFileManager defaultManager] removeItemAtPath:_storeURL.path error:nil];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
}
However this doesn't work, when performing a data refresh, it downloads the data but can't save it to the database and generates the following error in the console;
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
What's the "correct" way to refresh a database?

The "right" way to do this is to just fetch all of the objects, delete each of them, and then save the context. (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html)
- (void) deleteAllEntitiesForName:(NSString*)entityName {
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:entityName inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array != nil) {
for(NSManagedObject *managedObject in array) {
[moc deleteObject:managedObject];
}
error = nil;
[moc save:&error];
}
}
Then you can just recreate your objects.

Related

Update NSManagedObjectContext

Please consider the method at the end of this question. It attempts to delete all records from a CoreData entity.
The first -outcommented- part works fine: it deletes everything from my context, and then saves the context.
The second, non-commented part doesn't seem to work:
As a test I do
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"myEntity"];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"Fetch request returns %lu objects", (unsigned long)[results count]);
When I add records, and delete all records with the method below, the amount of records only increase.
As far as I understand, it actually works, but the context is not aware of it (yet).
So, therefore, in order to get a reliable context, I should update the context with
[NSManagedObjectContext mergeChangesFromRemoteContextSave:<#(nonnull NSDictionary *)#> intoContexts:self.managedObjectContext];
However, I don't have a clue what I should enter in the (nonnull NSDictionary *) part.
BTW: I only want to use the NSBatchDeleteRequest because I assume it is faster than iterating through all records.
- (void)deleteAllEntities:(NSString *)nameEntity
{
id appDelegate = (id)[[UIApplication sharedApplication] delegate];
self.managedObjectContext= [appDelegate managedObjectContext];
/*
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:nameEntity];
[fetchRequest setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError *error;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *object in fetchedObjects)
{
[self.managedObjectContext deleteObject:object];
}
error = nil;
[self.managedObjectContext save:&error];
*/
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:nameEntity];
NSBatchDeleteRequest *delete = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
NSError *deleteError = nil;
}

How to delete selected tables using persistentStoreCoordinator

I'm currently working on an IOS app which is already developed using objective-C.
I have added a module where users when login store details about the user. But as the app is already having some code, when I press the logout button it deletes all the entities from the database. For this they are using something like the code below.
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSError *error = nil;
// retrieve the store URL
NSURL *storeURL = [[managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
// lock the current context
[managedObjectContext lock];
[managedObjectContext reset];//to drop pending changes
//delete the store from the current managedObjectContext
if ([[managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error]){
// remove the file containing the data
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//recreate the store like in the appDelegate method
[[managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
}
[managedObjectContext unlock];
By keeping the break points, I understood that they are retrieving the url of the database and deleting it and re-creating it. Lets say they have 3 tables A,B and C, I want to delete A & B but not C. Reference- Persistent Store Coordinator
Is my understanding correct? How can I achieve this?
TIA
Try this code
NSManagedObjectContext *context = [self managedObjectContext];
[context deleteObject:managedObject];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
When user tap logout, you should clear the stored data.
First of all Fetch all the data using simple fetch store it in an array say "results"
then fetch objects in the array and remove it
for (Entity *entityOBJ in results) {
[context deleteObject:entityOBJ];
}
[context save:&error];
You have 3 tables, repeat it for 3 tables. Create a function just pass on the TableName.
I did something like below. Correct me If I'm wrong with my approach.
- (void) deleteData {
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSError *error = nil;
[managedObjectContext lock];
[managedObjectContext reset];
NSPersistentStoreCoordinator *psc = [managedObjectContext persistentStoreCoordinator];
NSManagedObjectModel *managedModel = [psc managedObjectModel];
NSArray *allEntityNames = [managedModel.entitiesByName allKeys];
for(NSString *entityName in allEntityNames)
{
//I wanted to delete all objects except for one table
if(![entityName isEqual:switchAccountsTableName])
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *objError = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&objError];
if(fetchedObjects == nil)
{
NSLog(#"Logout- Couldnt delete entity objects");
}
for(NSManagedObject *entityObj in fetchedObjects)
{
[managedObjectContext deleteObject:entityObj];
}
}
}
[managedObjectContext save:&error];
[managedObjectContext unlock];
}

Restkit/CoreData - Updating an entity's attribute does not update UITableView

I have an entity Country that includes an attribute downloaded which has a default value of 0, and is NOT mapped by RestKit. I want to be able to group my tableView into sections based on this downloaded attribute. Everything works as expected, until I try to programmatically change the downloaded value myself. Code below:
Where I'm trying to set the value - my context is the mainQueueManagedObjectContext that has been passed to the controller through the AppDelegate.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Country *country = [self.fetchedResultsController objectAtIndexPath:indexPath];
[FGDataCalls downloadFieldGuide:country.countryId];
//Set up to get the thing you want to update
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Country" inManagedObjectContext:self.managedObjectContext]];
[request setPredicate:[NSPredicate predicateWithFormat:#"countryId = %#", country.countryId]];
NSError *error = nil;
country = [[self.managedObjectContext executeFetchRequest:request error:&error] lastObject];
if (error) {
NSLog(#"Error getting the country from core data: %#", error);
}
country.downloaded = 1;
error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Saving changes to country failed: %#", error);
}
}
FetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Country" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Sort using the name property.
NSSortDescriptor *sortDownloaded = [[NSSortDescriptor alloc] initWithKey:#"downloaded" ascending:NO];
NSSortDescriptor *sortName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDownloaded, sortName]];
// Use the sectionIdentifier property to group into sections.
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"downloaded" cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
Initializing Restkit:
//Enable RestKit logging
//RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelInfo);
//RKLogConfigureByName("RestKit/CoreData", RKLogLevelTrace);
//RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
// Initialize RestKit
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:BaseURLString]];
// Initialize managed object store
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FGDataModel" ofType:#"momd"]];
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
BOOL success = RKEnsureDirectoryExistsAtPath(RKApplicationDataDirectory(), &error);
if (!success) {
RKLogError(#"Failed to create Application Data Directory at path '%#': %#", RKApplicationDataDirectory(), error);
}
objectManager.managedObjectStore = managedObjectStore;
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"FGDataModel.sqlite"];
NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:path fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
if (! persistentStore) {
RKLogError(#"Failed adding persistent store at path '%#': %#", path, error);
}
// Configure a managed object cache to ensure we do not create duplicate objects
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:managedObjectStore];
// Setup our object mappings
[FGDataCalls setupObjectMappings:managedObjectStore withObjectManager:objectManager];
// Save the MOC
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
appDelegate.managedObjectContext = managedObjectStore.mainQueueManagedObjectContext;
I've tried saving the context as many different ways at this point, including blocks, and I haven't yet had the fetchedResultsController recognize the update and even call controllerWillChangeContent: or controllerDidChangeContent:. I have successfully deleted a row using the MOC's deleteObject: method, so I have the context in the correct state. I can even get the object from my fetchedResultsController after the downloaded attribute has been set and see that it is correct, but the table never updates. Additionally, forcing the table to reload after I have changed the attribute doesn't yield any result. I know this is a specific case, but hopefully someone else has run into the same issue or can see where I'm going wrong.
Thanks!
When saving you shouldn't do
error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Saving changes to country failed: %#", error);
}
you should do
error = nil;
if (![self.managedObjectContext saveToPersistentStore:&error]) {
NSLog(#"Saving changes to country failed: %#", error);
}
That said, assuming the FRC is connected to the same context then it should see changes before they're saved. You also don't show your FRC delegate methods so there could be an issue there.

RestKit - Data not persisting between app launches

In my RestKit application, I need to store an email and password in CoreData (I know about NSUserDefaults, but I have other user properties I need stored in CoreData as well).
NSManagedObjectContext *context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSError *error = nil;
NSEntityDescription *userEntity = [NSEntityDescription
entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:userEntity];
NSArray *users = [context executeFetchRequest:request error:&error];
return (User *)[users firstObject];
This user object is always null at the application launch.
Then, when the user enters his/her information, I save it like so:
- (void)cacheEmailAndPassword:(NSDictionary *)credentials {
NSManagedObjectContext *context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSError *error = nil;
NSEntityDescription *userEntity = [NSEntityDescription
entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:userEntity];
NSArray *users = [context executeFetchRequest:request error:&error]; //should be a single object array for now
User *user = (User *)[users firstObject];
user.email = [credentials objectForKey:#"email"];
user.password = [credentials objectForKey:#"password"];
}
Afterwards, when I try to get the User data from the first function, it returns with all the information. However, when I relaunch the program, the data is null again. Why is this data not persisting between application launches?
EDIT: In case I screwed something up in my initialization, I've pasted it below:
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Model" ofType:#"momd"]];
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
// Initialize the Core Data stack
[managedObjectStore createPersistentStoreCoordinator];
// Configure the object manager
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:BASE_URL]];
objectManager.managedObjectStore = managedObjectStore;
NSPersistentStore __unused *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(persistentStore, #"Failed to add persistent store: %#", error);
[managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:managedObjectStore];
[RKObjectManager setSharedManager:objectManager];
This is how to add persistent storage
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"storage" ofType:#"momd"]];
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
BOOL success = RKEnsureDirectoryExistsAtPath(RKApplicationDataDirectory(), &error);
if (! success) {
RKLogError(#"Failed to create Application Data Directory at path '%#': %#", RKApplicationDataDirectory(), error);
}
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"MyStore.sqlite"];
NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:path fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
if (! persistentStore) {
RKLogError(#"Failed adding persistent store at path '%#': %#", path, error);
}
And whenever you are done with the changes, dont forget to save
if(![self.managedObjectContext saveToPersistentStore:&error]){
You never create a user anywhere in the code you posted. You first fetch all users - and none come back. Then you fetch them again - why do you expect that there is one now? Did you check if the user is nil the second time as well?
What you need to to is to insert a new User object if there is none:
User *user = [NSEntityDescription insertNewObjectForEntityForName:#"User"
inManagedObjectContext:context];
// configure the user
[context save:nil];

copying an sqlite file from one project to another

Following this tutorial http://www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated, I created an empty command line application using core data to seed a database which I subsequently moved (by taking the quizgeodata.sqlite file) over to an ios application I am building. The only other step the tutorial lists for making it work is to add this code to the persistantStoreCoordinator in app delegate
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"quizgeodata" ofType:#"sqlite"]];
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(#"Oops, could copy preloaded data");
}
}
(Previous steps included taking the class files (that correspond to the entities) and the ...xcdatamodel.d file into the empty command line application to make sure that the schemas would be the same). When I run the code in the empty command line application, it shows the fetched data that I imported in the log statements, so I assume by copying the quizgeodata.sqlite back to the ios application and adding that one bit of code in the persistent store coordinator, then the following code (in viewDidLoad) should fetch the data from core data, but the log statements are showing that no data's been retrieved. The log of the error in the code below says 'null' (meaning no error I assume) and when I ask xCode to print the sql statements it shows this in the console
2014-04-17 16:11:57.477 qbgeo[767:a0b] CoreData: annotation: total fetch execution time: 0.0026s for 0 rows.
so obviously there's no data in the sqlite file that I copied over. Can you explain what I might need to do to get it working?
Please note that I ran Project > Clean several times so that is not the issue
from ViewDidLoad
id delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = [delegate managedObjectContext];
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Sportsquiz" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"error %#", [error description]);
NSArray *messedUpObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (Sportsquiz *info in messedUpObjects) {
NSLog(#"correctAnswer %#", [info valueForKey:#"question"]);
NSLog(#"correctAnswer %#", [info valueForKey:#"correctAnswer"]);
}
Let's try:
The original project:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"TinMung.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSMutableDictionary *pragmaOptions = [NSMutableDictionary dictionary];
/*ATTETION: disable WAL mode*/
[pragmaOptions setObject:#"DELETE" forKey:#"journal_mode"];
NSNumber *optionYes = [NSNumber numberWithBool:YES];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
pragmaOptions, NSSQLitePragmasOption,
optionYes,NSMigratePersistentStoresAutomaticallyOption ,nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
abort();
}
return _persistentStoreCoordinator;
}
Copy sqlite file to new project and fetch. I think it will work.

Resources