Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force coredata to rebuild sqlite database model?

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!

like image 246
Burt Avatar asked Mar 01 '10 15:03

Burt


3 Answers

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.

like image 141
Marcus S. Zarra Avatar answered Nov 10 '22 21:11

Marcus S. Zarra


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;
}
like image 32
Alex Reynolds Avatar answered Nov 10 '22 22:11

Alex Reynolds


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];
like image 1
jessecurry Avatar answered Nov 10 '22 20:11

jessecurry