How to force coredata to rebuild sqlite database model?

15,206

Solution 1

The Core Data stack does not like you removing the file under it. If you are wanting to delete the file you should tear down the stack, delete the file and then reconstruct the stack. That will eliminate the issue.

Part of the problem is that the stack keeps a cache of the data that is in the file. When you remove the file you don't have a way to clear that cache and you are then putting Core Data into an unknown and unstable state.

You can try telling the NSPersistentStoreCoordinator you are removing the file with a call to -removePersistentStore:error: and then adding the new store with a call to -addPersistentStoreWithType:configuration:URL:options:error:. I am doing that currently in ZSync and it works just fine.

Solution 2

I use the following method -resetApplicationModel in my app delegate and it works fine for me.

You may not need the kApplicationIsFirstTimeRunKey user default, but I use it to test whether to populate the Core Data store with default settings in a custom method called -setupModelDefaults, which I also call from -applicationDidFinishLaunching: if the first-time run flag is YES.

- (BOOL) resetApplicationModel {

    // ----------------------
    // This method removes all traces of the Core Data store and then resets the application defaults
    // ----------------------

    [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:kApplicationIsFirstTimeRunKey];
    NSLog(@"Turned ON the first-time run flag...");

    NSError *_error = nil;
    NSURL *_storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"MyAppSQLStore.sqlite"]];
    NSPersistentStore *_store = [persistentStoreCoordinator persistentStoreForURL:_storeURL];

    //
    // Remove the SQL store and the file associated with it
    //
    if ([persistentStoreCoordinator removePersistentStore:_store error:&_error]) {
        [[NSFileManager defaultManager] removeItemAtPath:_storeURL.path error:&_error];
    }

    if (_error) {
        NSLog(@"Failed to remove persistent store: %@", [_error localizedDescription]);
        NSArray *_detailedErrors = [[_error userInfo] objectForKey:NSDetailedErrorsKey];
        if (_detailedErrors != nil && [_detailedErrors count] > 0) {
            for (NSError *_detailedError in _detailedErrors) {
                NSLog(@" DetailedError: %@", [_detailedError userInfo]);
            }                
        }
        else {
            NSLog(@" %@", [_error userInfo]);
        }
        return NO;
    }

    [persistentStoreCoordinator release], persistentStoreCoordinator = nil;
    [managedObjectContext release], managedObjectContext = nil;

    //
    // Rebuild the application's managed object context
    //
    [self managedObjectContext];

    //
    // Repopulate Core Data defaults
    //
    [self setupModelDefaults];

    return YES;
}

Solution 3

You can keep a "clean" copy of your sqlite database as part of the application bundle, then just copy over the version in the documents directory whenever you'd like to refresh the database.

Here's some code from an App that does something similar (although this version will not copy over and existing db):

// Check for the existence of the seed database
// Get the path to the documents directory and append the databaseName
NSString* databasePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kDatabaseName];

NSFileManager* fileManager = [NSFileManager defaultManager];
if ( ![fileManager fileExistsAtPath: databasePath] ) 
{
    NSString* databasePathFromApp = [[[NSBundle mainBundle] resourcePath] 
                                     stringByAppendingPathComponent: kDatabaseName];

    [fileManager copyItemAtPath: databasePathFromApp 
                         toPath: databasePath 
                          error: nil];
}
[fileManager release];
Share:
15,206
Burt
Author by

Burt

Updated on June 15, 2022

Comments

  • Burt
    Burt almost 2 years

    In my app I sometimes need to rebuild and repopulate database file. SQLite databse is created and managed by CoreData stack.

    What I'm trying to do is drop the file and then simply recreate persistentStoreCoordinator object.

    It works under simulator but not on device, where I'm getting such an error:

    NSFilePath = "/var/mobile/Applications/936C6CC7-423A-46F4-ADC0-7184EAB0CADD/Documents/MYDB.sqlite";
    NSUnderlyingException = I/O error for database at /var/mobile/Applications/936C6CC7-423A-46F4-ADC0-7184EAB0CADD/Documents/MYDB.sqlite.  SQLite error code:1, 'table ZXXXX already exists';
    

    I cannot find the cause of this in any way. It indicates two different problems - Cocoa error 256 indicates that file does not exist or is not readable. But file IS created after creating persistenStoreCoordinator, although it's empty, but after executing some queries it disappears.

    Second message indicating attempt to create alredy existing table is quite strange in that case.

    I'm quite confused and cannot get the point what's going on here. My code looks like this:

    NSString *path = [[WLLocalService dataStorePath] relativePath];
    NSError *error = nil;
    
    WLLOG(@"About to remove file %@", path);
    
    [[NSFileManager defaultManager] removeItemAtPath: path error: &error];
    
    if (error != nil) {
     WLLOG(@"Error removing the DB: %@", error);
    }
    
    
    [self persistentStoreCoordinator];
    
    WLLOG(@"Rebuild DB result %d", [[NSFileManager defaultManager] fileExistsAtPath: path]);
    

    After this code is exectued, DB file exists but is empty. When then first query (and all following) is executed, it gives me the error above and file disappears.

    Does anybody has an idea what's wrong with it?

    Big thanks for pointing me the right way!

  • Burt
    Burt about 14 years
    Thanks for a tip, it's interesting and I'm going to try it. But isn't it the same when all instances of NSPersistentStoreCoordinator are released just before the file is removed? That's what happens just before call to my method above. I'd suppose it will remove and free all subsequent data structures as well. Can you explain why it's important to explicitly remove persistent store from coordinator before removing the file itself?
  • Burt
    Burt about 14 years
    So I've tried your solution and it works! Big thanks for that! However I'm still wondering where's the difference between explicitly removing persistent store and simply releasing coordinator.
  • vond
    vond about 14 years
    If you set the NSPersistentStoreCoordinator to nil within the NSManagedObjectContext and release it, then you would probably accomplish the same thing but that is far heavier than just instructing the NSPersistentStoreCoordinator to remove the store.
  • Jeff Wolski
    Jeff Wolski over 12 years
    So, when asking the NSPersistentStoreCoordinator to remove the store, how do I figure out what NSPersistenStore to pass?
  • vond
    vond over 12 years
    You can look at the configuration which is a string, the path, etc. Or you can hold onto a reference to it when you construct the NSPersistentStoreCoordinator.
  • Ranjit
    Ranjit about 11 years
    Hi @Alex Reynolds, I want to reset all data in my coreData, I want to know, what the function [setupModeDefaults] do.?.also please have a look at this stackoverflow.com/questions/14646595/…
  • Alex Reynolds
    Alex Reynolds about 11 years
    It's just a method that repopulates the Core Data store with default values. You'd write your own method to put back in default values.
  • Ranjit
    Ranjit about 11 years
    thanks for responding, I tried your code, but it is not working.Will you please help me out
  • Ranjit
    Ranjit about 11 years
    I dont want to set it to any default values , So, I have decided not to use that function, So I am only calling [self managedObjectContext].So when I open my ViewController, my NSFetchResultsController, still has data , because of which I am getting error.