Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core data custom migration

My old core data model has an NSDate field, which I would like to change to a NSNumber. I read the Apple documentation and several similar questions on SO and other blogs (see references at end of question)

But no matter what I do, I keep getting the same error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Mismatch between mapping and source/destination models'

I only have 2 versions of the model, and I have verified time and again that the source and destination models are correct.

I even discarded all my changes and recreated a new model, mappings and entities (NSManagedObject subclasses). I've been stuck on this for almost 2 days now, and have no clue anymore as to what I'm doing. Any pointers on what I'm doing wrong will be greatly appreciated.

- (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;
}

My custom NSEntityMigration class:


- (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;
}

Some references:

  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/
like image 454
Neo Avatar asked Apr 25 '13 18:04

Neo


2 Answers

I admit I don't understand the cause of the error. In my migration I have one policy per entity and I am checking for the entity before using it. Not sure if this extra if will help you:

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

}

like image 115
Olaf Avatar answered Nov 10 '22 15:11

Olaf


Everything you are doing is way more complicated than it has to be. You can do all of this without migrating you database at all. You can add another property to your subclass that implements it:

///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;
    }
}

It is a little annoying to have duplicate properties (startDate and startDateNumber) but it is so much simpler and doesn't have any of the migration issues.

like image 29
Jon Rose Avatar answered Nov 10 '22 17:11

Jon Rose