I\'m trying to perform an iOS Core Data migration that requires a MappingModel. Core data is not able to use the mapping model for some reason and it falls back to an automa
Had the same issue. I deleted an entity and renamed the relationship fields accordingly. I first tried to used lightweight migration and therefore specified renaming IDs for the relationships. Due to an oversight I mixed up the fields used for the "Renaming ID" and the "Hash Modifier". Once corrected everything works as expected.
In your persistentStoreCoordinator method give this line of code
NSDictionary *options=[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,[NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption, nil];
If this doesnt help then you need to go for user implemented migration. So you will have to create a mapping model using source and destination model. In that case set,
NSDictionary *options=[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,[NSNumber numberWithBool:NO],NSInferMappingModelAutomaticallyOption, nil];
Create douce metadata with following code
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]);
}
and implement the following function
- (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;
}
The mapping model is probably not sufficient to handle the migration. In this case the mapping model will not be loaded, even if it matches source and destination model.
Write a test for the migration. Redo the changes to your model step by step and test the migration. That way you will find the change that does prevent the mapping model from loading.
Lookout for: Renaming attributes or creating attributes without specifying default values. Changing attributes to being non optional.
In those cases you have to specify the behavior manually within the mapping model.
The mapping model generated by Xcode 4 does not produce the correct hashes needed for the migration to occur. You can compare the output from the migration log to your mapping file's hashes with the code below:
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);
}
You'll see that these do not match the hashes in the migration log output.
The workaround is to generate the mapping file in Xcode 5.
After futher investigation I found out I was experiencing the same issue as mentioned here (Core Data migration fails for to-one relationship).
Settings the minimum to 1
instead of no minimum in my relationship made Core Data use my custom mapping model. While I'm not sure I assume this is a bug in Core Data.
However, this required me to change the original (1.0) mapping model (which users were already using) too.
The fix I came up with is to create a new mapping model between 1.0 and 2.0 called 1.5. The only thing that's different in 1.5 in comparison with 1.0 is the minimum of the relationship, which is in 1.5 set to 1
.
Now, I could let Core Data perform a lightweight migration from 1.0 to 1.5 and hereafter perform my own custom migration from 1.5 to 2.0.
Although it may be a hacky solution, it works perfectly. I pasted the code that handles the migration below.
- (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;
}