Implementing Batch Updates

Follow the implementation guidelines in this chapter to avoid common pitfalls and produce maintainable code. You will learn how to:

Impacts of Using a Batch Update

Batch updates run faster than processing the Core Data entities yourself in code because they operate in the persistent store itself, at the SQL level. As part of this difference, the changes enacted on the persistent store are not reflected in the objects that are currently in memory.

After a batch update has been executed, refresh any objects that are in memory that may be affected by the changes made in the persistent store.

Validation Rules

When you use batch updates any validation rules that are a part of the data model are not enforced when the batch update is executed. Therefore, ensure that any changes caused by the batch update will continue to pass the validation rules.

Setting Up a batch update

The goal of a batch update is to change one or more properties on a specific entity that is stored in a persistent store in Core Data. A batch update cannot be used to alter relationships, delete entities, or create new entities. To start a batch update, you create an NSBatchUpdateRequest object which has a number of similarities to an NSFetchRequest.

let request = NSBatchUpdateRequest(entityName: "Employee")
let predicate = NSPredicate(format: "salary > %@", 10000.00)
request.predicate = predicate

Creating an NSBatchUpdateRequest is nearly identical to creating an NSFetchRequest because the initial actions are the same: declare what entity is being accessed, filter down to the subset of entities that need to be accessed.

Once you have declared what entities you wish to update, you need to specify what changes need to be made:

request.propertiesToUpdate = ["terminationDate" : NSDate()]

The propertiesToUpdate dictionary can have one or more key/value pairs so that multiple changes can be performed during one execution. These changes cannot be calculated or dynamic in any way; that is, they cannot contain any logic beyond the filtering that is available in the predicate.

Executing a batch update

After the NSBatchUpdateRequest is constructed, it is executed against an NSManagedObjectContext:

do {
    let result = try moc.executeRequest(request)
} catch {
    fatalError("Failed to execute request: \(error)")

The call to executeRequest() can throw an error and therefore requires the try keyword. If the call fails, the error can be reported. When the executeRequest completes successfully, a response is received. That response can take one of several forms. The form of the response is determined by setting the resultType property on the NSBatchUpdateRequest. The default value is NSStatusOnlyResultType, which returns nothing. The other two options are:

The resultType needs to be set prior to the execution of the NSBatchUpdateRequest. Regardless of which resultType that is set, the execution of the NSBatchUpdateRequest returns an NSPersistentStoreResult instance. If the resultType is set to either NSUpdatedObjectIDsResultType or NSUpdatedObjectsCountResultType, the value of the result property inside of the NSPersistentStoreResult instance is set.

Updating Your Application After Execution

If the entities that are being updated are not loaded into memory, then there is no need to update your application with the changes. However, if you are making changes to entities in the persistence layer and you may have those entities in memory at the same time, it is important that you notify the application that the objects in memory are stale and need to be refreshed.

To do this, you first need to make sure the resultType of the NSBatchUpdateRequest is set to NSBatchUpdateRequestResultType.updatedObjectIDsResultType before the request is executed. When the request has completed successfully, the resulting NSPersistentStoreResult instance that is returned has an array of NSManagedObjectID instances referenced in the result property. That array of NSManagedObjectID instances can then be used to update one or more NSManagedObjectContext instances.

do {
    let result = try managedObjectContext.execute(request) as? NSBatchUpdateResult
    let objectIDArray = result?.result as? [NSManagedObjectID]
    let changes = [NSUpdatedObjectsKey : objectIDArray]
    NSManagedObjectContext.mergeChangesFromRemoteContextSave(changes, [moc])
} catch {
    fatalError("Failed to perform batch update: \(error)")

By calling mergeChangesFromRemoteContextSave, all of the NSManagedObjectContext instances that are referenced are notified that the list of entities referenced with the NSManagedObjectID array have changed and that the objects in memory are stale. This causes the referenced NSManagedObjectContext instances to refresh any objects they have loaded that match the NSManagedObjectID instances in the array.