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:
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With