Core Data Lightweight Migration

Introducing new attributes or entities in your data model requires that you create a new model version and, assuming the changes are not too complex, requires Core Data to perform a lightweight migration to upgrade the data in the old store to the new model.

Unfortunately it seems that when you have an OSX Core Data iCloud store with data imported from multiple peer devices and you try and perform a lightweight migration Core Data gets confused and does not correctly migrate all the data to the new store.

Fortunately this seems to work OK on iOS even though it takes considerable time to complete.

Lightweight migration of local stores on the other hand seems to work pretty flawlessly.  So it might be an idea to get your user to switch to using a local store before upgrading your app.

Ideally I want to implement a workflow that will automatically migrate the iCloud store to a local store, perform the upgrade and then migrate the upgraded store to iCloud.

OK here is how I have done this – no need to keep two copies of the model because it is possible to get the current model using the following call

_sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:metaData];

So as long as we use this _sourceModel we can open the existing store without any fear that an automatic lightweight migration will be performed by Core Data.

Here is a brief summary

On startup in - (BOOL)application:didFinishLaunchingWithOptions:

we call the OSCDStackManager.checkCDModelVersion method which determines if an upgrade is required and if so also gets the source model using the call described above.  If an upgrade is required we display an upgrade view while the upgrade is being performed instead of the main view.  Once the upgrade is completed we then display the usual main view.

In the UpgradeViewController we then perform the upgrade by going through the following key steps:

1. Set a bunch of flags so reused methods know not to perform certain actions, such as loading seed data because we are performing an upgrade. Migrate the store to a local file by calling OSCDStackManager.upgradeStep1

2.  Remove the iCloud store (to prevent any issues when migrating the upgraded local store back to iCloud.  Actually this gets done in step1 above already so this is a empty method (I got too lazy to separate the code for now).  You guessed it call OSCDStackManager.upgradeStep2!

3. Now open the migrated store using the new model to cause Core Data to perform a lightweight migration.  Close it.  All done in OSCDStackManager.upgradeStep2.

4. Migrate the upgraded store back to iCloud – make sure there is nothing in iCloud first !!  Potential for problems here if other devices are running the app live while this is going on so best to tell the user to delete the app from other devices before starting this upgrade.

5. Start using the App.

Not too hard after all.

2 thoughts on “Core Data Lightweight Migration

  1. I was able to find your sample app that you posted v4_18, but could not find the use of:
    _sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:metaData];

    The Tricky part is that in order to open the document, I need to feed it the right managed object model, but I can’t get a hold of that model without opening the document. Its the same question you posed on StackOverflow last October:

    I have all the transaction logs to build a document in the cloud waiting to be assembled, but I can’t build the document locally without the proper managedObjectModel. And to find out the proper managedObjectModel, I need to get the storeMetadata. But to get the store metadata I need the URL for the persistent store – which doesn’t seem to exist until I open the document locally. How do you get around this?

    Thanks for all your help Duncan!

    • Get the latest version 4_23 and you should find this in OSCDStackManager. The tricky part as you point out is that you need to get the store metadata without opening the store. I am saving the store file’s URL in NSUserDefaults so that when the App is started I know what the files URL is. Of course this means you have to have created the store file previously. If you have not then there is no store file to get metadata from – I have not investigated how to get metadata from the iCloud files if no local store exists. Not sure what User Scenario this would be necessary for – for example, if the user deleted the old version of the app and then installs a new version with a new model the app would need to build a new store from the iCloud baseline and log files. In any event in this scenario you can’t just migration the iCloud store to a local store before the upgrade because you don’t have an iCloud store built yet. This is not a scenario I have tested.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s