I'm having a problem performing a lightweight migration when migrating from a store that is defined by two separate xcdatamodel files.
In version 1.0 of my app, I had the models broken out into an analytics model, model-A, and everything else in model-B. When compiling, the models would be grouped together and everything proceeded smoothly.
When working on the new version, 1.1, I upgraded model-B by adding a new model version to model-B and setting that new version as active.
The issue arises when upgrading from 1.0 to 1.1. It seems Core Data checks the model store on disk (created by version 1.0) and looks for the model that describes it but is unable to find a SINGLE model that defines the entire store (model-A only covers analytics, and model-B covers everything else), so it throws a "Can’t find model for source store" error.
Has anyone found a solution for separating out models but still allowing upgrades + lightweight migrations to work without the extra hassle of defining custom migrations?
Here is the snippet of code used to load models:
NSArray *modelNames = [NSArray arrayWithObjects:@"model-A", @"model-B", nil];
NSMutableArray *models = [NSMutableArray array];
for (NSString *name in modelNames)
{
LogInfo(@"loading model %@", name);
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:name withExtension:@"momd"];
NSManagedObjectModel *model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];
[models addObject:model];
}
// combine all the separate models into one big one
objectModel = [[NSManagedObjectModel modelByMergingModels:models] retain];
NSURL *documentsDirectory = [NSURL fileURLWithPath:[SuperFileManager documentsDirectory] isDirectory:YES];
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:@"database.sqlite"];
NSError *error = nil;
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
After attending a WWDC 2012 lab and meeting with the Core Data team, it seems you are forced to put all your model info in a single xcdatamodel. CoreData is not intelligent enough to check its existing stores as a combination of the stores that created it and are still on disk. As C. Roald pointed out, you can do some processing on old xcdatamodel files, but it's quite sad that Core Data does not handle this more elegantly.
I encountered this problem also. I lost several hours trying to figure out WTF -- very frustrating.
I believe the easiest way to solve this problem is:
Pick which model you're keeping -- say ModelB -- and create a new version for it based on the published version. I'll call the published version ModelBv1 and the new version ModelBv1_merge.
Open contents XML files for ModelAv1 and ModelBv1_merge in a text editor (ie, ModelA.xcdatamodeld/ModelAv1.xcdatamodel/contents
and ModelB.xcdatamodeld/ModelBv1_merge.xcdatamodel/contents
) and merge the XML by hand. The schema is very simple -- just copy the <entity>
elements and merge the <elements>
element (into the _merge contents file) and you're done.
Open the contents file for your new ModelBv2 and again merge ModelA contents into it.
Remove ModelA from your project file.
Check in Xcode that ModelBv1_merge and ModelBv2 look sane, and contain everything you expect (the union of old Model A and Model B). Build and you should be done.
(I think this has a caveat of "provided both contents files were written by the same version of Xcode", but I think if you have an old contents file it should be easy enough to make Xcode rewrite it by making a trivial change somewhere.)
I have a scenario in which my application model is obtained merging multiple models, and I managed to have a kind of automatic lightweight migration in this way:
NSError* error = nil;
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:@"db.sqlite"];
NSString* storePath = [storeURL path];
NSLog(@"Store URL: %@", storeURL);
if( [[NSFileManager defaultManager] fileExistsAtPath:storePath] ){
// Load store metadata (this will contain information about the versions of the models this store was created with)
NSDictionary *storeMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:nil URL:storeURL error:&error];
if(storeMeta){
// Get the current model, merging all the models in the main bundle (in their current version)
NSManagedObjectModel* model=[NSManagedObjectModel mergedModelFromBundles:nil];
// If the persistent store is not compatible with such a model (i.e. it was created with a model obtained merging old versions of "submodels"), migrate
if(![model isConfiguration:nil compatibleWithStoreMetadata:storeMeta]){
// Load the old model
NSManagedObjectModel*oldModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:storeMeta];
// Compute the mapping between old model and new model
NSMappingModel* mapping = [NSMappingModel inferredMappingModelForSourceModel:oldModel destinationModel:model error:&error];
if(mapping){
// Backup old store
NSURL* storeBackupURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@"db.sqlite.%@.bck", [NSDate new]]];
BOOL done = [[NSFileManager defaultManager] moveItemAtURL:storeURL toURL:storeBackupURL error:&error];
if(done){
// Apply the mapping
NSMigrationManager* migrationManager = [[NSMigrationManager alloc] initWithSourceModel:oldModel destinationModel:model];
BOOL done = [migrationManager migrateStoreFromURL: storeBackupURL
type: NSSQLiteStoreType
options: nil
withMappingModel: mapping
toDestinationURL: storeURL
destinationType: NSSQLiteStoreType
destinationOptions: nil
error: &error];
if(done){
NSLog(@"Store migration successful!!!");
}
}
}
}
}
}
if(error){
NSLog(@"Migration error: %@", error);
}
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