Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Duplication of entity when change made by a child ManagedObjectContext is pushed (saved) to its parent

I expect (hope even) that this will be a stupid question, but after wrestling for many hours with this problem I need some insight.

My iOS 5.1 app uses nested MOCs, having a PrivateQueueConcurrency child MOC, call it MOC-Child, whose parent is a MainQueueConcurrency MOC, call it MOC-Parent. MOC-Parent backs a table view that displays its entities.

MOC-Child is used by a parser, which runs asynchronously on a non-main thread, to create new entities that correspond to XML entity-descriptions parsed from a URL connection, and then push the new entities to the MOC-Parent, via a save on the MOC-Child, which displays them in a table view. This works great:

1) Parser creates entity in MOC-Child
2) Parser saves MOC-Child
3) New entity is added to MOC-Parent
4) New entity is displayed in MOC-Parent's table view

However, the description of any given entity in this feed can change over time, so that the entity it describes needs to be modified in the app and its cell in the table view updated. So for each entity description in the feed, the parser attempts to fetch the entity (if any) in MOC-Child that has the same id as the described entity, and if there is one compares the values of the existing entity with description values to see if anything has changed. When the parser detects in this way that an entity has been modified, it updates the values of the existing entity in MOC-Child with the new values in the description, and then saves MOC-Child to push the changes up to MOC-Parent.

This is where things go wrong. I had thought that changes made to a fetched entity in MOC-Child would, when pushed to the parent by saving it, simply "appear" in the "same" entity in MOC-Parent. However, what I see happening is that the changed entity from MOC-Child is being added to MOC-Parent, as though it were a completely separate and new entity; with the result that MOC-Parent, and thus the table view it drives, ends up with two entities for every modified entity:

1) Parser modifies existing entity in MOC-Child
2) Parser saves MOC-Child
3) Modified entity is _added to_ MOC-Parent
4) Entity is displayed _twice_ in MOC-Parent's table view

What gives? What do I need to do to change values of an existing entity in MOC-Parent, if I can't do it within MOC-Child? And if I should be able to do so within MOC-Child, what if anything more do I need to do besides saving the MOC-Child to get the changes to be made in MOC-Parent without it adding the entity a second time?

Thanks in advance for any help or insight you can offer!

Carl

P.S. A couple of clarifications. When I say that the changed entity is added to the MOC-Parent, I mean that the NSFetchedResultsController that is monitoring the MOC-Parent issues a NSFetchedResultsChangeInsert change type (not a NSFetchedResultsChangeUpdate change type) for the changed entity, even though the "same" entity is already present in the MOC-Parent.

Also, when I say "same" entity, I mean an entity in MOC-Parent that has the same description-supplied id (in this case an NSString) as the entity that is changed in the MOC-child (not necessarily the same objectID, in fact apparently having a different objectId).

like image 901
Carl F. Hostetter Avatar asked Nov 14 '22 09:11

Carl F. Hostetter


1 Answers

I would imagine that, since this is such an old question, it's no longer an issue. But as I recently found myself wrestling with a similar situation, I figured I'd offer my solution.

I download an array of thousands of objects (which are associated by one or more barcodes) to an XML file, which I then parse and save in batches on a background context using an NSOperation subclass. The disk context (connected to the persistent store) listens for the NSManagedObjectContextDidSaveNotification, at which point it also saves (remember to do so in a block).

[self.diskManagedObjectContext performBlockAndWait:^{
    NSError *error;
    if (![self.diskManagedObjectContext save:&error]) {
        NSLog(@"error saving to disk: %@",error);
    }
}];

The tricky part is that any of those objects' barcodes might be scanned (in the UI context) before the XML file has been fully parsed and the information loaded into the database. And I have a table view backed by an NSFetchedResultsController that displays scanned objects, which show "unknown" in their titles until data is retrieved from the background load. Like in Carl's situation, these objects need to be updated when the background context saves.

To handle this, I use a three-context system similar to the one in this question, with a UI Context and background context, both of which are children of my main disk context (which is tied to the persistent store). So, during the XML parsing, I run a query on an object's barcodes in the background context (which also pulls from the parent disk context) to see if an object has already been created with that barcode. If it has, I merely update the information, re-save, reset the disk context, and call refreshObject:mergeChanges: from the UI context to pull these changes from the disk context. This successfully refreshes the table without creating duplicates.

I originally tried this using only two contexts, a background context and a UI context, but while I never ended up creating duplicate objects, I got really good at hanging the UI thread.

I guess the important question is How are you modifying the existing entity? And don't forget the reset.

like image 118
enjayem Avatar answered May 16 '23 07:05

enjayem