10

我正在尝试执行需要 MappingModel 的 iOS 核心数据迁移。核心数据由于某种原因无法使用映射模型,它会退回到自动轻量级迁移。

我启用了 MigrationDebug 选项以获取更多信息,而我看到的内容毫无意义。映射模型的源和目标哈希与源和目标 ManagedObjectModel 相同,忽略顺序。似乎应该使用映射模型,但日志显示“找不到合适的映射模型”。

这是(省略的)日志:

CoreData: annotation: (migration)    will attempt automatic schema migration
CoreData: annotation: (migration) looking for mapping model with 
 source hashes: 
{
    TSBaseEntity = <4797118c 50068f2f f544d9a9 4884720b 55ec7e4d 0d4c8f4e 1ee44be3 b06d2edc>;
    TSBuyer = <91e837d1 3f348913 eff634d6 6fb9b3a6 747e2390 fbdc4ae6 32cc56d6 7582d4a8>;
    ...
}
 destination hashes: {
    TSBaseEntity = <4797118c 50068f2f f544d9a9 4884720b 55ec7e4d 0d4c8f4e 1ee44be3 b06d2edc>;
    TSBuyer = <e316a857 8919c4be eef15387 5c67a21b 67d32919 99ead438 1ff93c05 2e065fcc>;
    ...
}
CoreData: annotation: (migration) checking mapping model at path file://localhost/Users/xandrews/Library/Application%20Support/iPhone%20Simulator/6.1/Applications/0A84951E-21FC-47C0-A1B7-F880ACB672C4/Dev.app/Migrate_0_5_24To_0_5_27.cdm
 source hashes: 
{(
    <4797118c 50068f2f f544d9a9 4884720b 55ec7e4d 0d4c8f4e 1ee44be3 b06d2edc>,
    <91e837d1 3f348913 eff634d6 6fb9b3a6 747e2390 fbdc4ae6 32cc56d6 7582d4a8>,
    ...
)}
 destination hashes: {(
    <4797118c 50068f2f f544d9a9 4884720b 55ec7e4d 0d4c8f4e 1ee44be3 b06d2edc>,
    <e316a857 8919c4be eef15387 5c67a21b 67d32919 99ead438 1ff93c05 2e065fcc>,
    ...
)}
CoreData: annotation: (migration) no suitable mapping model found
CoreData: annotation: (migration) inferring a mapping model between data models with 
 source hashes: ...
4

5 回答 5

8

Xcode 4 生成的映射模型不会产生迁移所需的正确哈希值。您可以使用以下代码将迁移日志的输出与映射文件的哈希值进行比较:

    NSString *mappingModelPath = [[NSBundle mainBundle] pathForResource:@"MappingFile" ofType:@"cdm"];
    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:mappingModelPath]];

    for (NSEntityMapping *entityMapping in mappingModel.entityMappings) {
        NSLog(@"%@: %@", entityMapping.sourceEntityName, entityMapping.sourceEntityVersionHash);
        NSLog(@"%@: %@", entityMapping.destinationEntityName, entityMapping.destinationEntityVersionHash);
    }

您会看到这些与迁移日志输出中的哈希值不匹配。

解决方法是在 Xcode 5 中生成映射文件。

于 2013-06-13T05:30:02.727 回答
3

在您的 persistentStoreCoordinator 方法中给出这行代码

 NSDictionary *options=[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,[NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption, nil];

如果这没有帮助,那么您需要进行用户实施的迁移。因此,您必须使用源模型和目标模型创建映射模型。在那种情况下,

NSDictionary *options=[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,[NSNumber numberWithBool:NO],NSInferMappingModelAutomaticallyOption, nil];

使用以下代码创建 douce 元数据

if (sourceMetadata) {
        NSString *configuration = nil;
        NSManagedObjectModel *destinationModel = [self.persistentStoreCoordinator managedObjectModel];

        //Our Source 1 is going to be incompatible with the Version 2 Model, our Source 2 won't be...
        BOOL pscCompatible = [destinationModel isConfiguration:configuration compatibleWithStoreMetadata:sourceMetadata];
        NSLog(@"Is the STORE data COMPATIBLE? %@", (pscCompatible==YES) ?@"YES" :@"NO");

        if (pscCompatible == NO) {
            migrationSuccess = [self performMigrationWithSourceMetadata:sourceMetadata toDestinationModel:destinationModel];
        }
    }
    else {
        NSLog(@"checkForMigration FAIL - No Source Metadata! \nERROR: %@", [error localizedDescription]);
    }

并实现以下功能

- (BOOL)performMigrationWithSourceMetadata :(NSDictionary *)sourceMetadata toDestinationModel:(NSManagedObjectModel *)destinationModel
{
    BOOL migrationSuccess = NO;
    //Initialise a Migration Manager...
    NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil
                                                                    forStoreMetadata:sourceMetadata];
    //Perform the migration...
    if (sourceModel) {
        NSMigrationManager *standardMigrationManager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                                                                      destinationModel:destinationModel];
        //Retrieve the appropriate mapping model...
        NSMappingModel *mappingModel = [NSMappingModel mappingModelFromBundles:nil
                                                                forSourceModel:sourceModel
                                                              destinationModel:destinationModel];
        if (mappingModel) {
            NSError *error = nil;
            NSString *storeSourcePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes.sqlite"];
            NSURL *storeSourceUrl = [NSURL fileURLWithPath: storeSourcePath];
            NSString *storeDestPath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes2.sqlite"];
            NSURL *storeDestUrl = [NSURL fileURLWithPath:storeDestPath];

            //Pass nil here because we don't want to use any of these options:
            //NSIgnorePersistentStoreVersioningOption, NSMigratePersistentStoresAutomaticallyOption, or NSInferMappingModelAutomaticallyOption
            NSDictionary *sourceStoreOptions = nil;
            NSDictionary *destinationStoreOptions = nil;

            migrationSuccess = [standardMigrationManager migrateStoreFromURL:storeSourceUrl
                                                                        type:NSSQLiteStoreType
                                                                     options:sourceStoreOptions
                                                            withMappingModel:mappingModel
                                                            toDestinationURL:storeDestUrl
                                                             destinationType:NSSQLiteStoreType
                                                          destinationOptions:destinationStoreOptions
                                                                       error:&error];
            NSLog(@"MIGRATION SUCCESSFUL? %@", (migrationSuccess==YES)?@"YES":@"NO");
        }
    }
    else {
        //TODO: Error to user...
        NSLog(@"checkForMigration FAIL - No Mapping Model found!");
        abort();
    }
    return migrationSuccess;
}
于 2013-05-15T08:33:37.150 回答
1

经过进一步调查,我发现我遇到了与此处提到的相同的问题(Core Data migration failed for to-one relationship)。在我的关系中将最小值设置为1而不是无最小值使 Core Data 使用我的自定义映射模型。虽然我不确定我认为这是 Core Data 中的一个错误。
但是,这也需要我更改原始 (1.0) 映射模型(用户已经在使用)。我想出的解决方法是在 1.0 和 2.0 之间创建一个称为 1.5 的新映射模型。与 1.0 相比,1.5 中唯一不同的是关系的最小值,在 1.5 中设置为1. 现在,我可以让 Core Data 执行从 1.0 到 1.5 的轻量级迁移,然后执行我自己的从 1.5 到 2.0 的自定义迁移。尽管它可能是一个 hacky 解决方案,但它可以完美运行。我在下面粘贴了处理迁移的代码。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (persistentStoreCoordinator != nil) {
        return persistentStoreCoordinator;
    }

    NSURL *storeUrl = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Project.sqlite"]];
    NSError *error;

    NSDictionary *storeMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeUrl error:&error];

    if (! [[self managedObjectModel] isConfiguration:nil compatibleWithStoreMetadata:storeMetadata]) {
        // The current store isn't compatible with the model and should be migrated, check the version identifier of the store

        NSManagedObjectModel *sourceManagedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:storeMetadata];

        if ([[sourceManagedObjectModel versionIdentifiers] containsObject:@"Model_1_0"]) {
            // The current version of the store is 1.0, a manual migration to 1.5 is needed in order to migrate finally to 2.0

            NSURL *destinationModelURL = [[NSBundle mainBundle] URLForResource:@"Model.momd/Model_1_5" withExtension:@"mom"];
            NSManagedObjectModel *destinationManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:destinationModelURL];

            NSMigrationManager *migrationManager = [[NSMigrationManager alloc] initWithSourceModel:sourceManagedObjectModel destinationModel:destinationManagedObjectModel];
            NSMappingModel *inferredMappingModel = [NSMappingModel inferredMappingModelForSourceModel:sourceManagedObjectModel destinationModel:destinationManagedObjectModel error:&error];

            NSURL *destinationURL = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Migration.sqlite"]];

            [migrationManager migrateStoreFromURL:storeUrl
                                             type:NSSQLiteStoreType
                                          options:nil
                                 withMappingModel:inferredMappingModel
                                 toDestinationURL:destinationURL
                                  destinationType:NSSQLiteStoreType
                               destinationOptions:nil
                                            error:&error];
            if (error) {
                DLog(@"Failed to migrate store from URL %@ with mapping model %@ to destination URL %@ with error %@", storeUrl, inferredMappingModel, destinationURL, error);
            }

            [[NSFileManager defaultManager] removeItemAtURL:storeUrl error:&error];
            [[NSFileManager defaultManager] moveItemAtURL:destinationURL toURL:storeUrl error:&error];
        }
    }

    NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: [NSNumber numberWithBool:YES],
                              NSInferMappingModelAutomaticallyOption: [NSNumber numberWithBool:NO] };
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
        /*Error for store creation should be handled in here*/
        DLog(@"%@", error);
    }

    return persistentStoreCoordinator;
}
于 2013-05-15T15:10:50.437 回答
0

映射模型可能不足以处理迁移。在这种情况下,不会加载映射模型,即使它匹配源模型和目标模型。

为迁移编写测试。逐步重做对模型的更改并测试迁移。这样,您会发现确实阻止映射模型加载的更改。

注意:重命名属性或创建属性而不指定默认值。将属性更改为非可选。

在这些情况下,您必须在映射模型中手动指定行为。

于 2013-05-07T15:49:26.577 回答
0

有同样的问题。我删除了一个实体并相应地重命名了关系字段。我首先尝试使用轻量级迁移,因此为关系指定了重命名 ID。由于疏忽,我混淆了用于“重命名 ID”和“哈希修饰符”的字段。一旦更正,一切都会按预期工作。

于 2013-05-13T10:22:21.383 回答