Версионирование CoreData и блокировка облегченной миграции - PullRequest
0 голосов
/ 06 марта 2012

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

Что я хочу сделать сейчас, так это внести изменения в мою модель, которые теоретически облегченная миграция могла бы обрабатывать, но на самом деле мне нужны новые данные изсервер.Я хочу как-то пометить модель и не обновлять ее с помощью облегченной миграции.Например, если имя поля не изменилось, но значение этого поля изменилось таким образом, что старый код несовместим с новой базой кода.(Это только пример.)

Кто-нибудь нашел способ пометить две модели как несовместимые, чтобы облегченная миграция их не обновляла?

Ответы [ 2 ]

3 голосов
/ 06 марта 2012

Я боролся с той же проблемой раньше.

У меня есть метод, который попытается перенести данные с использованием картографических моделей, и это то, что вам следует использовать, если вы собираетесь отключить облегченную миграцию.

Если вы не собираетесь делать много причудливого отображения данных, xcode автоматически создаст модель отображения, которая будет работать точно так же, как облегченная миграция. Все, что вам нужно сделать, это создать новый файл «Модель отображения» каждый раз, когда вы добавляете новую версию в Core Data. Просто зайдите в «Файл -> Новый -> Новый файл» и в разделе «Основные данные» должен быть шаблон «Модель отображения». Выберите его и выберите исходную и целевую версии.

У меня нет открытого кода на github, поэтому я просто опубликую способ миграции здесь.

- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type toModel:(NSManagedObjectModel*)finalModel
{
    NSError *error = nil;

    // if store dosen't exist skip migration
    NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    if(![NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir])
    {
        migrationProgress = 1.0;
        [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];

        // remove migration view
        [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
        [self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES];

        self.migrationView = nil;
        self.migrationProgressLabel = nil;
        self.migrationProgressView = nil;
        self.migrationSpinner = nil;

        return YES;
    }

    //START:progressivelyMigrateURLHappyCheck
    NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type URL:sourceStoreURL error:&error];

    if (!sourceMetadata)
    {
        return NO;
    }

    if ([finalModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
    {
        migrationProgress = 1.0;
        [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];

        // remove migration view
        [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
        [self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES];

        self.migrationView = nil;
        self.migrationProgressLabel = nil;
        self.migrationProgressView = nil;
        self.migrationSpinner = nil;

        error = nil;
        return YES;
    }
    else
    {
        migrationProgress = 0.0;
        [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:NO waitUntilDone:YES];
        [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];        
    }
    //END:progressivelyMigrateURLHappyCheck

    //START:progressivelyMigrateURLFindModels
    //Find the source model
    NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata];
    if(sourceModel == nil)
    {
        NSLog(@"%@", [NSString stringWithFormat:@"Failed to find source model\n%@", [sourceMetadata description]]);
        return NO;
    }

    //Find all of the mom and momd files in the Resources directory
    NSMutableArray *modelPaths = [NSMutableArray array];
    NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd" inDirectory:nil];
    for (NSString *momdPath in momdArray)
    {
        NSAutoreleasePool *pool = [NSAutoreleasePool new];
        NSString *resourceSubpath = [momdPath lastPathComponent];
        NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:resourceSubpath];
        [modelPaths addObjectsFromArray:array];
        [pool drain];
    }

    NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:nil];
    [modelPaths addObjectsFromArray:otherModels];

    if (!modelPaths || ![modelPaths count])
    {
        //Throw an error if there are no models
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        [dict setValue:@"No models found in bundle" forKey:NSLocalizedDescriptionKey];

        //Populate the error
        error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict];
        if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
        {
            NSLog(@"error: %@", error);
        }
        return NO;
    }
    //END:progressivelyMigrateURLFindModels

    //See if we can find a matching destination model
    //START:progressivelyMigrateURLFindMap
    NSMappingModel *mappingModel = nil;
    NSManagedObjectModel *targetModel = nil;
    NSString *modelPath = nil;

    for(modelPath in modelPaths)
    {
        targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
        mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:targetModel];

        //If we found a mapping model then proceed
        if(mappingModel)
        {
            break;
        }
        else
        {
            //Release the target model and keep looking
            [targetModel release];
            targetModel = nil;
        }
    }

    //We have tested every model, if nil here we failed
    if (!mappingModel)
    {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        [dict setValue:@"No mapping models found in bundle" forKey:NSLocalizedDescriptionKey];
        error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict];
        if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
        {
            NSLog(@"error: %@", error);
        }
        return NO;
    }
    //END:progressivelyMigrateURLFindMap

    //We have a mapping model and a destination model.  Time to migrate
    //START:progressivelyMigrateURLMigrate
    NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel];

    // reg KVO for migration progress
    [manager addObserver:self forKeyPath:@"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL];

    NSString *modelName = [[modelPath lastPathComponent] stringByDeletingPathExtension];
    NSString *storeExtension = [[sourceStoreURL path] pathExtension];
    NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];

    //Build a path to write the new store
    storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath, modelName, storeExtension];
    NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath];

    if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error])
    {
        if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
        {
            NSLog(@"error: %@", error);
        }
        [targetModel release];
        [manager removeObserver:self forKeyPath:@"migrationProgress"];
        [manager release];
        return NO;
    }
    [targetModel release];
    [manager removeObserver:self forKeyPath:@"migrationProgress"];
    [manager release];
    //END:progressivelyMigrateURLMigrate

    //Migration was successful, move the files around to preserve the source
    //START:progressivelyMigrateURLMoveAndRecurse
    NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
    guid = [guid stringByAppendingPathExtension:modelName];
    guid = [guid stringByAppendingPathExtension:storeExtension];
    NSString *appSupportPath = [storePath stringByDeletingLastPathComponent];
    NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager moveItemAtPath:[sourceStoreURL path] toPath:backupPath error:&error])
    {
        if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
        {
            NSLog(@"error: %@", error);
        }
        //Failed to copy the file
        return NO;
    }

    //Move the destination to the source path
    if (![fileManager moveItemAtPath:storePath toPath:[sourceStoreURL path] error:&error])
    {
        if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
        {
            NSLog(@"error: %@", error);
        }
        //Try to back out the source move first, no point in checking it for errors
        [fileManager moveItemAtPath:backupPath toPath:[sourceStoreURL path] error:nil];
        return NO;
    }

    //We may not be at the "current" model yet, so recurse
    return [self progressivelyMigrateURL:sourceStoreURL ofType:type toModel:finalModel];
    //END:progressivelyMigrateURLMoveAndRecurse
}

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

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

Вы можете использовать этот метод так:

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

// perform core data migrations if necessary
if(![self progressivelyMigrateURL:storeURL ofType:NSSQLiteStoreType toModel:self.managedObjectModel])
{
    // reset the persistent store on fail
    NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    NSError *error = nil;
    [[NSFileManager defaultManager] removeItemAtPath:[NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir] error:&error];
}
else
{
    NSLog(@"migration succeeded!");
}

Не забудьте удалить опцию облегченной миграции, прежде чем использовать это.

0 голосов
/ 19 апреля 2012

Apple Руководство по программированию версий базовой модели данных и миграции данных описывает, что делать, если «у вас есть две версии модели, которые Core Data обычно рассматривали бы как эквивалентные, которые вы хотите, чтобы их распознавали как отличающиеся»Я думаю, это то, что вы спрашиваете.

Ответ заключается в том, чтобы установить versionHashModifier для одного из ваших объектов или свойств в новой модели.

В вашем примере вы бы сделаличто на поле, значение которого изменилось.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...