Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance considerations on deleting Managed Objects using Cascade rule in Core Data

I searched within SO but I didn't find a any suggestions to boost performances on deleting Managed Object in Core Data when dealing with relationships.

The scenario is quite simple.enter image description here

As you can see there are three different entities. Each entity is linked in cascade with the next. For example, FirstLevelhas a relationship called secondLevels to SecondLevel. The delete rule from FirstLevel to SecondLevel is Cascade while the delete rule from SecondLevel to FirstLevel is Nullify. The same rules are applied between SecondLevel and ThirdLevel.

When I want to delete the entire graph, I perform a method like the following:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"FirstLevel" inManagedObjectContext:context];
[fetchRequest setEntity:entity];

NSError *error = nil;
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];    

// delete roots object and let Core Data to do the rest...
for (NSManagedObject *managedObject in items) {
    [context deleteObject:managedObject];
}

Taking advantage of the Cascade rule the graph is removed. This works fast for few objects but decreases performances for many objects. In addition, I think (but I'm not very sure) that this type of deletion performs a lot of round trips to the disk, Am I wrong?

So, my question is the following: how is it possible to remove the graph without taking advantage of Cascade rule and boost performances but, at the same time, maintain graph consistency?

Thank you in advance.

EDIT

I cannot delete the entire file since I have other entities in my model.

EDIT 2

The code I posted is wrapped in the main method of a NSOperation subclass. This solution allows the deleting phase to be excuted in background. Since I took advantage of Cascade Rule the deletion is performed in a semi automatic way. I only delete root objects, the FirstLevel items, by means of the for loop within the posted code. In this way Core Data to do the rest for me. What I'm wondering is the following: is it possible to bybass that semi-automatic delete operation and do it manually without losing the graph consistency?

like image 416
Lorenzo B Avatar asked Apr 17 '12 08:04

Lorenzo B


1 Answers

You can't do much about the time it takes to traverse and remove the objects in the database. However, you can do it in the background so the UI does not get blocked.

For example, something like (code assumes ARC - and was just typed - not compiled)...

- (void)deleteAllLevelsInMOC:(NSManagedObjectContext*)moc
{
    // Create a context with a private queue, so access happens on a separate thread.
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    // Insert this context into the current context hierarchy
    context.parentContext = context;
    // Execute the block on the queue of the context
    context.performBlock:^{
            NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"FirstLevel"];
            // This will not load any properties - just object id
            fetchRequest.includesPropertyValues = NO;
            // Iterate over all first-level objects, deleting each one
            [[context executeFetchRequest:fetchRequest error:0] 
                enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                [context deleteObject:obj];
            }];
            // Push the changes out of the context
            [context save:0];
    }];
}

Note, that you could add a RootLevel object to your data model, and give it a one-to-many relationship to first level objects. Then, (as long as you only have one root object) all you have to do is delete that one root object, and it will delete everything else inside CoreData. If you have lots of FirstLevel objects, that would be the way to go.

Alternatively, you could connect your "new" context directly to the persistent store, make the changes, and have the other context watch for change notifications.

BTW, if you are using UIManagedDocument, you get this kind of backgrounding for free, because there is already a parent context running on its own queue, and all changes to the main context are passed off to the parent to actually do the database work.

EDIT

You can do it manually, but you have to turn off the cascade rule. I have found that I want CoreData to do as much for me as possible. It reduces my margin for error.

If you are already deleting in a background thread, then how are you seeing performance issues? You must be querying during that deletion... which means you are using a MOC that does all the work.

You should take a lesson from UIManagedDocument. you should have a MOC that runs on a private queue. It does all the real work. You have a child MOC that just passes work to that worker queue.

If you are actually querying for objects not in the graph you are deleting, they could get hung up by the serializing properties of the persistent store coordinator. If that is the case, you should consider either a separate coordinator, or just have two stores.

That way, you can be deleting the objects in your graph as long as you want, while still being responsive to requests for the other objects.

like image 73
Jody Hagins Avatar answered Nov 13 '22 16:11

Jody Hagins