An NSBatchDeleteRequest can be used to efficiently delete a batch of entries from a Core Data SQLite persistent store. It runs faster than deleting Core Data entities yourself on a managed object context as they operate at the SQL level in the persistent store itself. Its usage, however, is a bit less simple compared to the usual delete method which you can use on a managed object context.
So before we dive in and start creating an NSBatchDeleteRequest, we first go over some common pitfalls to give you all the information you need for executing batch deletions.
Common pitfalls when using an NSBatchDeleteRequest
Setting up an NSBatchDeleteRequest is fairly simple. It requires either passing in an NSFetchRequest
instance or a collection of managed object identifiers. After that, you simply execute the batch delete request on your managed object context. In the end, you’ve prepared yourself for debugging Core Data, so what can go wrong? Well, it all seems to be easy until you dive in and start using it more intensively.
Updating in-memory objects
After executing the NSBatchDeleteRequest you’ll find out that your entities still exist in memory. This is a result of the fact that the NSBatchDeleteRequest is an NSPersistentStoreRequest
which operates at the SQL level in the persistent store itself. Therefore, you need to manually update your in-memory objects after execution.
This can be done quite easily by calling the mergeChanges
method on your managed object context.
let fetchRequest = NSFetchRequest(entityName: "Content")
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
batchDeleteRequest.resultType = .resultTypeObjectIDs
let result = try execute(batchDeleteRequest) as! NSBatchDeleteResult
let changes: [AnyHashable: Any] = [
NSDeletedObjectsKey: result.result as! [NSManagedObjectID]
]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [myManagedObjectContext])
This will update your in-memory object and as well send out notifications like NSManagedObjectContextObjectsDidChange
.
Validation rules like Core Data relationships are ignored
Entity relationships need to be deleted manually. Any deletion rules you’ve set on an entity will not be respected. Therefore, you could easily end up with a Core Data error telling you that your persistent store is in an invalid state. The following error is an example of that:
CoreData: error: Unhandled error from executeBatchDeleteRequest Constraint violation:
Batch delete failed due to mandatory OTO nullify inverse on ContentSyncedInfo/content and userInfo {
"_NSCoreDataOptimisticLockingFailureConflictsKey" = (
""
);
}
A solution would be to use multiple batch delete requests to manually delete each relationship as well. However, in this case, it might be easier to go for the regular delete method which does respect validation rules.
Initializing with Object IDs requires the same entity
Another common pitfall is to initialize an NSBatchDeleteRequest with a collection of NSManagedObjectID
instances from different entities. You’ll soon enough encounter the following error:
mismatched objectIDs in batch delete initializer
(null)
Invalid formatIndex
It basically comes down to the fact that you’re allowed to only use object IDs for objects of the same entity. It’s too bad that the Apple documentation is not really informative here. It’s good to point out that Apple does have some good documentation which does quote the following related information:
The goal of a batch delete is to delete one or more specific entities that are stored in a SQLite persistent store
It clearly points out “specific entities” which makes sense looking at the above error.
Putting it all together in a handy extension
Most of the pitfalls can’t be solved with a handy extension. We can, however, make it easy to execute a batch delete request and directly update the in-memory objects. We do this by creating a simple extension on NSManagedObjectContext
.
extension NSManagedObjectContext {
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
batchDeleteRequest.resultType = .resultTypeObjectIDs
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}
You simply pass your NSBatchDeleteRequest instance into this method which will automatically call the mergeChanges
method on the given managed object context.
let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "Content")
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
managedObjectContext.executeAndMergeChanges(using: batchDeleteRequest)
Conclusion
After all, you have to ask yourself whether you can use an NSBatchDeleteRequest for your deletion. This should be easy by taking into account the following points. In other words, don’t use an NSBatchDeleteRequest if:
- The entity you’re deleting contains validation rules like relationships
- You’re deleting multiple different kinds of entities
- You are not using an SQLite backing store
Hopefully, the above extension makes it all a bit easier to implement and use batch delete requests with Core Data. In the end, it’s a very performant way of deleting a batch of Core Data entities.
Thanks!