Как заставить coredata восстановить базу данных sqlite?

В моем приложении мне иногда нужно перестроить и повторно заполнить файл базы данных. SQLite databse создается и управляется стеком CoreData.

То, что я пытаюсь сделать, это удалить файл, а затем просто воссоздать объект persistentStoreCoordinator.

Он работает под симулятором, но не на устройстве, где я получаю такую ​​ошибку:

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';

Я не могу найти причину этого в любом случае. Это указывает на две разные проблемы - ошибка Cocoa 256 указывает, что файл не существует или не читается. Но файл IS создан после создания persistenStoreCoordinator, хотя он пуст, но после выполнения некоторых запросов он исчезает.

Второе сообщение, указывающее на попытку создания существующей таблицы alredy, довольно странно в этом случае.

Я очень смущен и не могу понять, что здесь происходит. Мой код выглядит следующим образом:

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]);

После того, как этот код будет выведен, файл DB существует, но пуст. Когда выполняется первый запрос (и все последующие), он дает мне ошибку выше, и файл исчезает.

Кто-нибудь знает, что с ним не так?

Большое спасибо за то, что указали мне правильный путь!

Ответ 1

Стек Core Data не нравится, когда вы удаляете файл под ним. Если вы хотите удалить файл, вы должны снести стек, удалить файл, а затем восстановить стек. Это устранит проблему.

Частично проблема заключается в том, что в стеке хранится кеш данных, находящихся в файле. Когда вы удаляете файл, у вас нет способа очистить этот кеш, и вы затем помещаете Core Data в неизвестное и неустойчивое состояние.

Вы можете попросить NSPersistentStoreCoordinator удалить файл с вызовом -removePersistentStore:error:, а затем добавить новый магазин с вызовом -addPersistentStoreWithType:configuration:URL:options:error:. Я делаю это в настоящее время в ZSync, и он работает нормально.

Ответ 2

Я использую следующий метод -resetApplicationModel в своем делете приложения, и он отлично работает для меня.

Вам может не понадобиться пользовательский параметр kApplicationIsFirstTimeRunKey, но я использую его для проверки того, нужно ли заполнять хранилище основных данных стандартными настройками в пользовательском методе -setupModelDefaults, который я также вызываю из -applicationDidFinishLaunching:, если первый флаг времени выполнения 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 managed object context
    //
    [self managedObjectContext];

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

    return YES;
}

Ответ 3

Вы можете сохранить "чистую" копию вашей базы данных sqlite как часть пакета приложений, а затем просто скопировать версию в каталоге документов всякий раз, когда вы хотите обновить базу данных.

Вот код из приложения, который делает что-то подобное (хотя эта версия не будет копировать и существующую 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];