19

I have seen many questions regarding batch deletion in Core Data, but none seem to address my issue.

I am creating an iOS 9 / Swift app using Core Data. At WWDC this year, I attended the Core Data session and saw that I could use NSBatchDeleteRequest to delete large numbers of objects directly from the persistent store. This works for me for some objects but not others, and I think it has something to do with my relationships.

I have an object graph consisting of Subject and Course, where there is a one-to-many relationship. Subjects may own as many courses as they wish.

There is a 'courses' relationship on Subject with a delete rule of Cascade, as I want all courses associated with a subject to be deleted when a subject is deleted.

The inverse is 'subject' on Course, with a delete rule of Nullify. Here, I am a bit confused as to Apple's description of Nullify:

Remove the relationship between the objects but do not delete either object. This only makes sense if the department relationship for an employee is optional, or if you ensure that you set a new department for each of the employees before the next save operation.

That makes it pretty clear, but why would the relationships be deleted but not either object? If I delete a Course, I would like the Course to be deleted and the relationship from the subject to the course to be deleted so that a fault to the deleted Course will not appear in the NSSet on Subject's courses Set.

I want to provide a way for all objects in an entity to be deleted. When I try this by individually fetching and deleting each course, courses are properly deleted and removed from the NSSet of courses on a Subject.

Since I have no idea how many courses will be present and I want to ensure high performance in every situation, I figured I would use batch deletion to delete all courses. The problem is that while utilizing NSBatchDeleteRequest to delete all Subjects works fine, deleting all courses along the way (because of the Cascade rule), trying to delete all Courses using this method appears to leave all objects in place.

I used NSBatchDeleteRequest to delete all Courses, but then when I query the MOC to see what Subjects and Courses still exist, both Courses are still returned and the Subject owning them still has references to them.

In contrast, when I fetch and delete each Course individually, my subsequent fetch properly displays an empty array for all Courses and the 'courses' relationship on the Subject appears to have been properly modified.

Yes, I am saving the context after executing the request. I suppose the context may not be notified of what the store does, but then again deleting all subjects worked great. What is going on here?

Nikolay Suvandzhiev
  • 8,465
  • 6
  • 41
  • 47
Matthew Seaman
  • 7,952
  • 2
  • 37
  • 47
  • 1
    Batch deletes disregard delete rules. https://stackoverflow.com/questions/32915874/can-i-use-nsbatchdeleterequest-on-entities-with-relationships-that-have-delete-r/46073965#46073965 – LShi Sep 06 '17 at 11:28

2 Answers2

36

At the WWDC 2015 session which describes NSBatchDeleteRequest it was explained that "Changes are not reflected in the context". So what you're seeing is normal. Batch updates work directly on the persistent store file instead of going through the managed object context, so the context doesn't know about them. When you delete the objects by fetching and then deleting, you're working through the context, so it knows about the changes you're making (in fact it's performing those changes for you).

If you use NSBatchDeleteResultTypeObjectIDs, you can merge the results of the batch delete back into your context using mergeChangesFromRemoteContextSave:intoContexts: to update your contexts. You could probably also use reset, if you don't have any other managed objects loaded from the context.

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • Thanks! I used reset and it worked. Before seeing your answer, I did see that Courses were missing from the SQLite database and the app reflected this upon relaunch. – Matthew Seaman Nov 05 '15 at 00:45
  • I would like to switch to the mergeChanges method for safety. I have a copy of the batch deletion result which I have set to come back as `NSBatchDeleteResultTypeObjectID`. What do I pass for `changeNotificationData: [NSObject: AnyObject]` on `mergeChangesFromRemoteContextSave`? – Matthew Seaman Nov 05 '15 at 01:40
  • Okay instead of using `NSBatchDeleteResultTypeObjectID`, I registered for the `NSManagedObjectContextDidSaveNotification` and called `mergeChangesFromRemoteContextSave` from the block. – Matthew Seaman Nov 05 '15 at 03:16
  • @MatthewS Does that work for you? According to the documentation and the session video (WWDC 2015) that shouldn't work. A batch delete request bypasses the managed object context, which means that no notifications are sent that reflect the changes made by the batch delete request. – Bart Jacobs Nov 26 '15 at 09:24
  • @BartJacobs Yes, that works for me. The changes appear to be reflected in the context using this notification-observing method. It may be because I am calling `executeRequest` directly on my context instance. – Matthew Seaman Nov 27 '15 at 06:35
2

I ended up adding the following to take care of the context after the batch delete.

[self.managedObjectContext refreshAllObjects];
BuLB JoBs
  • 841
  • 4
  • 20
skantner
  • 480
  • 4
  • 19