Core Data: Delete All Objects of an Entity Type, Ie Clear a Table

Core Data: delete all objects of an entity type, ie clear a table

Dave DeLong is an expert at, well, just about everything, and so I feel like I'm telling Jesus how to walk on water. Granted, his post is from 2009, which was a LONG time ago.

However, the approach in the link posted by Bot is not necessarily the best way to handle large deletes.

Basically, that post suggests to fetch the object IDs, and then iterate through them, calling delete on each object.

The problem is that when you delete a single object, it has to go handle all the associated relationships as well, which could cause further fetching.

So, if you must do large scale deletes like this, I suggest adjusting your overall database so that you can isolate tables in specific core data stores. That way you can just delete the entire store, and possibly reconstruct the small bits that you want to remain. That will probably be the fastest approach.

However, if you want to delete the objects themselves, you should follow this pattern...

Do your deletes in batches, inside an autorelease pool, and be sure to pre-fetch any cascaded relationships. All these, together, will minimize the number of times you have to actually go to the database, and will, thus, decrease the amount of time it takes to perform your delete.

In the suggested approach, which comes down to...

  1. Fetch ObjectIds of all objects to be deleted
  2. Iterate through the list, and delete each object

If you have cascade relationships, you you will encounter a lot of extra trips to the database, and IO is really slow. You want to minimize the number of times you have to visit the database.

While it may initially sound counterintuitive, you want to fetch more data than you think you want to delete. The reason is that all that data can be fetched from the database in a few IO operations.

So, on your fetch request, you want to set...

[fetchRequest setRelationshipKeyPathsForPrefetching:@[@"relationship1", @"relationship2", .... , @"relationship3"]];

where those relationships represent all the relationships that may have a cascade delete rule.

Now, when your fetch is complete, you have all the objects that are going to be deleted, plus the objects that will be deleted as a result of those objects being deleted.

If you have a complex hierarchy, you want to prefetch as much as possible ahead of time. Otherwise, when you delete an object, Core Data is going to have to go fetch each relationship individually for each object so that it can managed the cascade delete.

This will waste a TON of time, because you will do many more IO operations as a result.

Now, after your fetch has completed, then you loop through the objects, and delete them. For large deletes you can see an order of magnitude speed up.

In addition, if you have a lot of objects, break it up into multiple batches, and do it inside an auto release pool.

Finally, do this in a separate background thread, so your UI does not pend. You can use a separate MOC, connected to a persistent store coordinator, and have the main MOC handle DidSave notifications to remove the objects from its context.

WHile this looks like code, treat it as pseudo-code...

NSManagedObjectContext *deleteContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateConcurrencyType];
// Get a new PSC for the same store
deleteContext.persistentStoreCoordinator = getInstanceOfPersistentStoreCoordinator();

// Each call to performBlock executes in its own autoreleasepool, so we don't
// need to explicitly use one if each chunk is done in a separate performBlock
__block void (^block)(void) = ^{
NSFetchRequest *fetchRequest = //
// Only fetch the number of objects to delete this iteration
fetchRequest.fetchLimit = NUM_ENTITIES_TO_DELETE_AT_ONCE;
// Prefetch all the relationships
fetchRequest.relationshipKeyPathsForPrefetching = prefetchRelationships;
// Don't need all the properties
fetchRequest.includesPropertyValues = NO;
NSArray *results = [deleteContext executeFetchRequest:fetchRequest error:&error];
if (results.count == 0) {
// Didn't get any objects for this fetch
if (nil == results) {
// Handle error
}
return;
}
for (MyEntity *entity in results) {
[deleteContext deleteObject:entity];
}
[deleteContext save:&error];
[deleteContext reset];

// Keep deleting objects until they are all gone
[deleteContext performBlock:block];
};

[deleteContext preformBlock:block];

Of course, you need to do appropriate error handling, but that's the basic idea.

Fetch in batches if you have so much data to delete that it will cripple memory.
Don't fetch all the properties.
Prefetch relationships to minimize IO operations.
Use autoreleasepool to keep memory from growing.
Prune the context.
Perform the task on a background thread.

If you have a really complex graph, make sure you prefetch all the cascaded relationships for all entities in your entire object graph.

Note, your main context will have to handle DidSave notifications to keep its context in step with the deletions.

EDIT

Thanks. Lots of good points. All well explained except, why create the
separate MOC? Any thoughts on not deleting the entire database, but
using sqlite to delete all rows from a particular table? – David

You use a separate MOC so the UI is not blocked while the long delete operation is happening. Note, that when the actual commit to the database happens, only one thread can be accessing the database, so any other access (like fetching) will block behind any updates. This is another reason to break the large delete operation into chunks. Small pieces of work will provide some chance for other MOC(s) to access the store without having to wait for the whole operation to complete.

If this causes problems, you can also implement priority queues (via dispatch_set_target_queue), but that is beyond the scope of this question.

As for using sqlite commands on the Core Data database, Apple has repeatedly said this is a bad idea, and you should not run direct SQL commands on a Core Data database file.


Finally, let me note this. In my experience, I have found that when I have a serious performance problem, it is usually a result of either poor design or improper implementation. Revisit your problem, and see if you can redesign your system somewhat to better accommodate this use case.

If you must send down all the data, perhaps query the database in a background thread and filter the new data so you break your data into three sets: objects that need modification, objects that need deletion, and objects that need to be inserted.

This way, you are only changing the database where it needs to be changed.

If the data is almost brand new every time, consider restructuring your database where these entities have their own database (I assume your database already contains multiple entities). That way you can just delete the file, and start over with a fresh database. That's fast. Now, reinserting several thousand objects is not going to be fast.

You have to manage any relationships manually, across stores. It's not difficult, but it's not automatic like relationships within the same store.

If I did this, I would first create the new database, then tear down the existing one, replace it with the new one, and then delete the old one.

If you are only manipulating your database via this batch mechanism, and you do not need object graph management, then maybe you want to consider using sqlite instead of Core Data.

Core Data: Quickest way to delete all instances of an entity

iOS 9 and later:

iOS 9 added a new class called NSBatchDeleteRequest that allows you to easily delete objects matching a predicate without having to load them all in to memory. Here's how you'd use it:

Swift 5

let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Car")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)

do {
try myPersistentStoreCoordinator.execute(deleteRequest, with: myContext)
} catch let error as NSError {
// TODO: handle the error
}

Objective-C

NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Car"];
NSBatchDeleteRequest *delete = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];

NSError *deleteError = nil;
[myPersistentStoreCoordinator executeRequest:delete withContext:myContext error:&deleteError];

More information about batch deletions can be found in the "What's New in Core Data" session from WWDC 2015 (starting at ~14:10).

iOS 8 and earlier:

Fetch 'em all and delete 'em all:

NSFetchRequest *allCars = [[NSFetchRequest alloc] init];
[allCars setEntity:[NSEntityDescription entityForName:@"Car" inManagedObjectContext:myContext]];
[allCars setIncludesPropertyValues:NO]; //only fetch the managedObjectID

NSError *error = nil;
NSArray *cars = [myContext executeFetchRequest:allCars error:&error];
[allCars release];
//error handling goes here
for (NSManagedObject *car in cars) {
[myContext deleteObject:car];
}
NSError *saveError = nil;
[myContext save:&saveError];
//more error handling here

Delete/Reset all entries in Core Data?

You can still delete the file programmatically, using the NSFileManager:removeItemAtPath:: method.

NSPersistentStore *store = ...;
NSError *error;
NSURL *storeURL = store.URL;
NSPersistentStoreCoordinator *storeCoordinator = ...;
[storeCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];

Then, just add the persistent store back to ensure it is recreated properly.

The programmatic way for iterating through each entity is both slower and prone to error. The use for doing it that way is if you want to delete some entities and not others. However you still need to make sure you retain referential integrity or you won't be able to persist your changes.

Just removing the store and recreating it is both fast and safe, and can certainly be done programatically at runtime.

Update for iOS5+

With the introduction of external binary storage (allowsExternalBinaryDataStorage or Store in External Record File) in iOS 5 and OS X 10.7, simply deleting files pointed by storeURLs is not enough. You'll leave the external record files behind. Since the naming scheme of these external record files is not public, I don't have a universal solution yet. – an0 May 8 '12 at 23:00

CoreData - delete all entities consumes RAM and take a long time

the thing that takes a lot of memory is that you are loading all sessions :

NSArray *allSessions = [self.context executeFetchRequest:sessionsRequest error:&sessionsError];

try to load for example 100 by 100 like this :

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[self.context setUndoManager:nil];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Session" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setFetchLimit:100];
NSError *error;
NSArray *items = [self.context executeFetchRequest:fetchRequest error:&error];
while ([items count] > 0) {
@autoreleasepool {
for (NSManagedObject *item in items) {
[self.context deleteObject:item];
}
if (![self.context save:&error]) {
NSLog(@"Error deleting %@ - error:%@",self.entityName, error);
}
}
items = [self.context executeFetchRequest:fetchRequest error:&error];
}

How to delete every Core Data entity without Faulting errors?

This should work for you

[ClassName MR_truncateAll];

[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];

ClassName here is the NSManagedObject subclass for your entity.

Why are Core Data NSManagedObject faults fired upon deletion?

I think an authoritative answer would require a look at the Core Data source code. Since that's not likely to be forthcoming, here are some reasons I can think of that this might be necessary.

  • For entities that have relationships, it's probably necessary to examine the relationship to handle delete rules and maintain data integrity. For example if the delete rule is "cascade", it's necessary to fire the fault to figure out what related instances should be deleted. If it's "nullify", fire the fault to figure out which related instances need to have their relationship value set to nil.
  • In addition to the above, entities with relationships need to have validation checks performed on related instances. For example if you delete an object with a relationship that uses the "nullify" delete rule, and the inverse relationship is not optional, you would fail the validation check on the inverse relationship. Checking this likely triggers firing the fault.
  • Binary attributes can have data automatically stored in external files (the "allows external storage" option). In order to clean up the external file, it's probably necessary to fire the fault, in order to know which file to delete.

I think all of these could probably be optimized away. For example, don't fire faults if the entity has no relationships and has no attributes that use external storage. However, this is looking from the outside without access to source code. There might be other reasons that require firing the fault. That seems likely. Or it could be that nobody has attempted this optimization, for whatever reason. That seems less likely but is possible.

BTW I forked your playground code to get a version that doesn't rely on an external data model file, but instead builds the model in code.

After getting the array of managed objects to delete, how do I delete ALL of them in the array?

You have to loop over your array of managed objects and delete them one by one. Core Data provides no batch delete operation. Note that when you send the deleteObject: message to your managed object context the object will be marked for deletion. The actual delete is performed when the context is saved.

Understanding Core Data delete rules on One to Many

If you set the delete rule to "nullify" and delete the A object, then the references to that object in the Bs will be removed. The inverse works the same way. If you have cascade and delete B then it will remove the A that B pointed to. It will then follow the delete rule from A to the remaining Bs (either cascade or nullify).

The rules you set really depend on your data model. If A were a customer and B were their orders then you could set the A->B rule to deny (prevent A from being deleted if it the customer has orders) or cascade (delete the orders when the customer is deleted). The B->A rule would probably be "nullify". If an order is deleted simply remove the reference to the order from the customer.

The relationship delete rules are described in the Apple Core Data Programming Guide

Completely Reset CoreData

In iOS9 and above you can use destroyPersistentStore and optionally add a new one

func destroyAllData(storeType : String = NSSQLiteStoreType) throws {
guard let storeURL = persistentStoreCoordinator.persistentStores.last?.url else {
print("Missing store URL")
return
}
try persistentStoreCoordinator.destroyPersistentStore(at: storeURL, ofType: storeType)
// try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: nil, at: storeURL)
}


Related Topics



Leave a reply



Submit