Customizing the Migration Process
You only customize the migration process if you want to initiate migration yourself. You might do this to, for example, to search for models in locations other than the application’s main bundle, or to deal with large data sets by performing the migration in several passes using different mapping models (see Multiple Passes—Dealing With Large Datasets).
Is Migration Necessary
Before you initiate a migration process, you should first determine whether it is necessary. You can check with NSManagedObjectModel
’s isConfiguration:compatibleWithStoreMetadata:
as illustrated in Listing 7-1.
Listing 7-1 Checking whether migration is necessary
NSPersistentStoreCoordinator *psc = /* get a coordinator */ ; |
NSString *sourceStoreType = /* type for the source store, or nil if not known */ ; |
NSURL *sourceStoreURL = /* URL for the source store */ ; |
NSError *error = nil; |
NSDictionary *sourceMetadata = |
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:sourceStoreType |
URL:sourceStoreURL |
error:&error]; |
if (sourceMetadata == nil) { |
// deal with error |
} |
NSString *configuration = /* name of configuration, or nil */ ; |
NSManagedObjectModel *destinationModel = [psc managedObjectModel]; |
BOOL pscCompatibile = [destinationModel |
isConfiguration:configuration |
compatibleWithStoreMetadata:sourceMetadata]; |
if (pscCompatibile) { |
// no need to migrate |
} |
Initializing a Migration Manager
You initialize a migration manager using initWithSourceModel:destinationModel:
; you therefore first need to find the appropriate model for the store. You get the model for the store using NSManagedObjectModel
’s mergedModelFromBundles:forStoreMetadata:
. If this returns a suitable model, you can create the migration manager as illustrated in Listing 7-2 (this code fragment continues from Listing 7-1).
Listing 7-2 Initializing a Migration Manager
NSArray *bundlesForSourceModel = /* an array of bundles, or nil for the main bundle */ ; |
NSManagedObjectModel *sourceModel = |
[NSManagedObjectModel mergedModelFromBundles:bundlesForSourceModel |
forStoreMetadata:sourceMetadata]; |
if (sourceModel == nil) { |
// deal with error |
} |
MyMigrationManager *migrationManager = |
[[MyMigrationManager alloc] |
initWithSourceModel:sourceModel |
destinationModel:destinationModel]; |
Performing a Migration
You migrate a store using NSMigrationManager
’s migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error:
. To use this method you need to marshal a number of parameters; most are straightforward, the only one that requires some work is the discovery of the appropriate mapping model (which you can retrieve using NSMappingModel
’s mappingModelFromBundles:forSourceModel:destinationModel:
method). This is illustrated in Listing 7-3 (a continuation of the example shown in Listing 7-2).
Listing 7-3 Performing a Migration
NSArray *bundlesForMappingModel = /* an array of bundles, or nil for the main bundle */ ; |
NSError *error = nil; |
NSMappingModel *mappingModel = |
[NSMappingModel |
mappingModelFromBundles:bundlesForMappingModel |
forSourceModel:sourceModel |
destinationModel:destinationModel]; |
if (mappingModel == nil) { |
// deal with the error |
} |
NSDictionary *sourceStoreOptions = /* options for the source store */ ; |
NSURL *destinationStoreURL = /* URL for the destination store */ ; |
NSString *destinationStoreType = /* type for the destination store */ ; |
NSDictionary *destinationStoreOptions = /* options for the destination store */ ; |
BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL |
type:sourceStoreType |
options:sourceStoreOptions |
withMappingModel:mappingModel |
toDestinationURL:destinationStoreURL |
destinationType:destinationStoreType |
destinationOptions:destinationStoreOptions |
error:&error]; |
Multiple Passes—Dealing With Large Datasets
The basic approach shown above is to have the migration manager take two models, and then iterate over the steps (mappings) provided in a mapping model to move the data from one side to the next. Because Core Data performs a "three stage" migration—where it creates all of the data first, and then relates the data in a second stage—it must maintain “association tables" (which tell it which object in the destination store is the migrated version of which object in the source store, and vice-versa). Further, because it doesn't have a means to flush the contexts it is working with, it means you'll accumulate many objects in the migration manager as the migration progresses.
In order to address this, the mapping model is given as a parameter of the migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error:
call itself. What this means is that if you can segregate parts of your graph (as far as mappings are concerned) and create them in separate mapping models, you could do the following:
Get the source and destination data models
Create a migration manager with them
Find all of your mapping models, and put them into an array (in some defined order, if necessary)
Loop through the array, and call
migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error:
with each of the mappings
This allows you to migrate "chunks" of data at a time, while not pulling in all of the data at once.
From a "tracking/showing progress” point of view, that basically just creates another layer to work from, so you'd be able to determine percentage complete based on number of mapping models to iterate through (and then further on the number of entity mappings in a model you've already gone through).
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-01-09