9
votes

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/
2
@Nishant Did you try setting the com.apple.CoreData.MigrationDebug preference to 1?Willeke
@Willeke Yes I did that. That doesnt explicitly tell me why this Mismatch between mappings is happening.Nishant
It is very difficult to tell from here why you get the error. Ask a new question and tell what you changed to the data model, what you changed to the default mapping model, what you did in code and what the similar issue is.Willeke
@Nishant. I notice in your "createDestinationInstanceFromSourceInstance" method, you create a new managed object of type n, where n can be potentially any type given by the entity mapping object. You are then assuming the newObject of that entity type has the properties "startDate", "endDate", "creationTime", "timeStamp". It may be the mapping only contains one destination entity type, so this may not be your problem (or every entity type in your model may have those properties), but I would put in some entity type checking before attempting to set those properties if I were you.TheBasicMind
@TheBasicMind What if I dont implement 'createDestinationInstancesForSourceInstance' at all. I just want multiple mappings for migration for large data I have. Custom migration is not necessary. Whats the right approach for that?Nishant

2 Answers

1
votes

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;

}

1
votes

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.