Core Data Database Migration Explained
So, you have your application wrapped up and ready for sale. It is being released, but suddenly, you realize that the old database is not sufficient for the things you want to do in your updates. What do you do then? Read on, and you will see.
Core Data relies heavily on SQLite system (provided you are using the SQLite type of NSPersistentStoreCoordinator). If you need some more information about the internals of Core Data, you can read a brief introduction here. You will need those concepts in order to proceed.
Core Data stores are bound to the managed object model used to create them. You know, that . xcdatamodel file in your project? Yes, that’s the one. Since this model describes the structure of the SQL data stored in your database, changing a model will render it incompatible with the stores it previously created. So, when updating your application, and trying to load a .sqlite file created with the old model’s persistent store, your application will crash. And by stores, we mean the NSPersistentStoreCoordinator object created with each application run. If you change your schema, you therefore need to migrate the data in existing stores to new version.
When developing iMe, I came into the position of having to update my database to support some internal references to variables, in order to maintain compatibility with the old iMe, and not breaking compatibility with the new features implemented. I made some simple changed to the model, but, as you may already know, the new database is not compatible with the old one.
In order to make the migration, you will use a method described as Model Versioning.
In order to update your database, you will need to create a managed object model bundle. The managed object model bundle is a bundle which holds different versions of your model, and that knows which of the available versions is the current one. It also allows storage of mapping models; a mapping model translates the old version of the model to the new version by specifying which source entities map to which destination entities, similarly which attributes match and also relationships. It even allows you to specify custom mapping objects that let you run code to fill out information that doesn’t just map straight across.
In order to use mapping models, you can head over to Apple’s documentation, which is very straight-forward. However, for simple things, like adding some new properties to entities, and changing one or two relationships, the simpler method described here will be more suitable, and less painful.
First of all, create the managed object model bundle that will hold your old and new database model. In Xcode, select your . xcdatamodel file and go into Design->Data Model->Add Model Version, and Xcode will create the bundle for you, putting inside your original model file, and a new one, which is exactly the same as your old one. In the new one, you will commit your changes.
Make your changes, then select your newly created xcdatamodel, go into Design->Data Model and choose “Set Current Version” to tell Xcode, that the selected model is the want to use from now on.
But you are not done yet! You need to tell your application to migrate from the old model to the new one. For that, you only need to change the code that creates your NSPersistentStoreCoordinator. Here is the code:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSLog(@"initializing persistentStoreCoordinator");
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"iMeApp1.sqlite"]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Handle error
NSLog(@"Problem with PersistentStoreCoordinator: %@",error);
}
return persistentStoreCoordinator;
}
As you can see, we create the Persistent Store Coordinator using a dictionary with values and keys this time. The two keys are pretty much self-explained and they should cause any problem. Build your program, and run it. You can have many different versions of a model, but I advise you to keep them to a minimum. It should make things easier for you, and will be less error-prone.