核心数据自定义迁移

时间:2013-04-25 18:37:06

标签: ios objective-c core-data core-data-migration

我的旧核心数据模型有NSDate字段,我想将其更改为NSNumber。我在SO和其他博客上阅读了Apple文档和几个类似的问题(参见问题末尾的参考文献)

但无论我做什么,我都会遇到同样的错误:

  

由于未捕获的异常'NSInvalidArgumentException'而终止应用程序,原因:'映射和源/目标模型之间不匹配'

我只有2个版本的模型,我已经一次又一次地验证了源模型和目标模型的正确性。

我甚至放弃了所有更改并重新创建了一个新模型,映射和实体(NSManagedObject子类)。我现在已经坚持了近2天,并且不知道我在做什么。关于我做错的任何指示都将不胜感激。

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

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

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    NSString *sourceStoreType = NSSQLiteStoreType;
    NSURL *sourceStoreURL = storeURL;

    NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"New.sqlite"];
    NSString *destinationStoreType = NSSQLiteStoreType;
    NSDictionary *destinationStoreOptions = nil;

    NSDictionary *sourceMetadata =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:sourceStoreType
                                                               URL:sourceStoreURL
                                                             error:&error];

    if (sourceMetadata == nil) {
        NSLog(@"source metadata is nil");
    }

    NSManagedObjectModel *destinationModel = [_persistentStoreCoordinator managedObjectModel];
    BOOL pscCompatibile = [destinationModel
                           isConfiguration:nil
                           compatibleWithStoreMetadata:sourceMetadata];

    if (pscCompatibile) {
        // no need to migrate
        NSLog(@"is compatible");
    } else {
        NSLog(@"is not compatible");

        NSManagedObjectModel *sourceModel =
        [NSManagedObjectModel mergedModelFromBundles:nil
                                    forStoreMetadata:sourceMetadata];

        if (sourceModel != nil) {
            NSLog(@"source model is not nil");

            NSMigrationManager *migrationManager =
            [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                           destinationModel:destinationModel];

            NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"MyMigrationMapping" withExtension:@"cdm"];
            NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

            NSArray *newEntityMappings = [NSArray arrayWithArray:mappingModel.entityMappings];
            for (NSEntityMapping *entityMapping in newEntityMappings) {
                entityMapping.entityMigrationPolicyClassName = NSStringFromClass([ConvertDateToNumberTransformationPolicy class]);
            }
            mappingModel.entityMappings = newEntityMappings;

            BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                                       type:sourceStoreType
                                                    options:nil
                                           withMappingModel:mappingModel
                                           toDestinationURL:destinationStoreURL
                                            destinationType:destinationStoreType
                                         destinationOptions:nil
                                                      error:&error];

            if (ok) {
                storeURL = destinationStoreURL;
            }
        } else {
            NSLog(@"e nil source model");
        }
    }

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

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    

    return _persistentStoreCoordinator;
}

我的自定义NSEntityMigration课程:


- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
                                      entityMapping:(NSEntityMapping *)mapping
                                            manager:(NSMigrationManager *)manager
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject =
    [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
                                  inManagedObjectContext:[manager destinationContext]];

    NSArray *arrayOfKeys = @[@"startDate", @"endDate", @"creationTime", @"timeStamp"];

    for (NSString *key in arrayOfKeys) {
        // do our transfer of NSDate to NSNumber
        NSDate *date = [sInstance valueForKey:key];
        NSLog(@"Key: %@, value: %@", key, [date description]);

        // set the value for our new object
        [newObject setValue:[NSNumber numberWithDouble:[date timeIntervalSince1970]] forKey:key];
    }

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

一些参考文献:

  1. Example or explanation of Core Data Migration with multiple passes?
  2. Core Data - Default Migration ( Manual )
  3. http://www.preenandprune.com/cocoamondo/?p=468
  4. http://www.timisted.net/blog/archive/core-data-migration/

2 个答案:

答案 0 :(得分:1)

我承认我不明白错误的原因。在我的迁移中,每个实体都有一个策略,我在使用之前检查实体。不确定这个额外的if是否会对您有所帮助:

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
                                  entityMapping:(NSEntityMapping *)mapping
                                        manager:(NSMigrationManager *)manager
                                          error:(NSError **)error {

    NSEntityDescription *sourceInstanceEntity = [sInstance entity];
   if ([[sInstance name] isEqualToString:@"<-name-of-entity>"] ) {
       newObject = [NSEntityDescription insertNewObjectForEntityForName:@"<-name-of-entity>"
                       inManagedObjectContext:[manager destinationContext]];
       NSArray *arrayOfKeys = @[@"startDate", @"endDate", @"creationTime", @"timeStamp"];

      for (NSString *key in arrayOfKeys) {
           // do our transfer of NSDate to NSNumber
           NSDate *date = [sInstance valueForKey:key];
           NSLog(@"Key: %@, value: %@", key, [date description]);

          // set the value for our new object
          [newObject setValue:[NSNumber numberWithDouble:[date timeIntervalSince1970]] forKey:key];
      }
   }

// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

return YES;

}

答案 1 :(得分:1)

你正在做的每件事情都比以前复杂得多。您可以在不迁移数据库的情况下完成所有这些操作。您可以向实现它的子类添加另一个属性:

///in your .h
@property(nonatomic, copy) NSNumber* startDateNumber
/// in you .m
-(NSNumber*) startDateNumber{
    if (self.startDate) {
        return @(self.startDate.timeIntervalSince1970);
    }
    return nil;
}
-(void)setStartDateNumber:(NSNumber*)startDateNumber{
    if(startDateNumber){
        self.startDate =[NSDate dateWithTimeIntervalSince1970:startDateNumber.doubleValue];
    }else{
        self.startDate = nil;
    }
}

拥有重复的属性(startDatestartDateNumber)有点烦人,但它更简单,没有任何迁移问题。