ios 核心数据:删除一个实体类型的所有对象,即清空一个表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12113961/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 19:51:54  来源:igfitidea点击:

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

iossqlitecore-data

提问by David

This has been asked before, but no solution described that is fast enough for my app needs.

之前已经问过这个问题,但没有描述足够快的解决方案来满足我的应用程序需求。

In the communications protocol we have set up, the server sends down a new set of all customers every time a sync is performed. Earlier, we had been storing as a plist. Now want to use Core Data.

在我们设置的通信协议中,每次执行同步时,服务器都会发送一组新的所有客户。早些时候,我们一直存储为 plist。现在想使用核心数据。

There can be thousands of entries. Deleting each one individually takes a long time. Is there a way to delete all rows in a particular table in Core Data?

可以有成千上万的条目。单独删除每一个需要很长时间。有没有办法删除核心数据中特定表中的所有行?

delete from customer

This call in sqlite happens instantly. Going through each one individually in Core Data can take 30 seconds on an iPad1.

sqlite 中的这个调用会立即发生。在 iPad1 上单独浏览 Core Data 中的每一个可能需要 30 秒。

Is it reasonable to shut down Core Data, i.e. drop the persistence store and all managed object contexts, then drop into sqlite and perform the delete command against the table? No other activity is going on during this process so I don't need access to other parts of the database.

关闭Core Data是否合理,即删除持久性存储和所有托管对象上下文,然后放入sqlite并对表执行删除命令?在此过程中没有其他活动在进行,因此我不需要访问数据库的其他部分。

回答by Jody Hagins

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.

Dave DeLong 是几乎所有方面的专家,所以我觉得我在告诉耶稣如何在水上行走。当然,他的帖子是从 2009 年开始的,那是很久以前的事了。

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

但是,Bot 发布的链接中的方法不一定是处理大量删除的最佳方法。

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

基本上,该帖子建议获取对象 ID,然后遍历它们,对每个对象调用 delete。

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
  1. 获取所有要删除的对象的ObjectIds
  2. 遍历列表,并删除每个对象

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.

如果你有级联关系,你会遇到很多额外的数据库行程,IO真的很慢。您希望尽量减少必须访问数据库的次数。

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.

虽然最初听起来可能违反直觉,但您想要获取的数据比您想删除的要多。原因是所有这些数据都可以通过一些 IO 操作从数据库中获取。

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.

如果您有一个复杂的层次结构,您希望尽可能提前预取。否则,当您删除一个对象时,Core Data 将不得不为每个对象单独获取每个关系,以便它可以管理级联删除。

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

这将浪费大量时间,因为您将因此执行更多 IO 操作。

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.

最后,在单独的后台线程中执行此操作,这样您的 UI 就不会挂起。您可以使用单独的 MOC,连接到持久存储协调器,并让主 MOC 处理 DidSave 通知以从其上下文中删除对象。

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.

如果要删除的数据太多,会削弱内存,请分批获取。不要获取所有属性。预取关系以最小化 IO 操作。使用 autoreleasepool 来防止内存增长。修剪上下文。在后台线程上执行任务。

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.

请注意,您的主上下文必须处理 DidSave 通知,以使其上下文与删除保持同步。

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

谢谢。很多优点。一切都很好解释,除了为什么要创建单独的 MOC?关于不删除整个数据库,而是使用 sqlite 删除特定表中的所有行的任何想法?- 大卫

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.

您使用单独的 MOC,因此在执行长删除操作时不会阻止 UI。请注意,当对数据库的实际提交发生时,只有一个线程可以访问数据库,因此任何其他访问(如获取)都将阻止任何更新。这是将大型删除操作分成块的另一个原因。小块工作将为其他 MOC 提供一些机会来访问商店,而无需等待整个操作完成。

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

如果这会导致问题,您还可以实现优先级队列(通过dispatch_set_target_queue),但这超出了本问题的范围。

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.

至于在 Core Data 数据库上使用 sqlite 命令,Apple 一再表示这是一个坏主意,你不应该在 Core Data 数据库文件上直接运行 SQL 命令。



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.

如果您只是通过这种批处理机制来操作您的数据库,并且您不需要对象图管理,那么您可能要考虑使用 sqlite 而不是 Core Data。

回答by Suragch

iOS 9 and later

iOS 9 及更高版本

Use NSBatchDeleteRequest. I tested this in the simulator on a Core Data entity with more than 400,000 instances and the delete was almost instantaneous.

使用NSBatchDeleteRequest. 我在具有超过 400,000 个实例的 Core Data 实体上的模拟器中对此进行了测试,并且删除几乎是瞬间完成的。

// fetch all items in entity and request to delete them
let fetchRequest = NSFetchRequest(entityName: "MyEntity")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)

// delegate objects
let myManagedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let myPersistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator

// perform the delete
do {
    try myPersistentStoreCoordinator.executeRequest(deleteRequest, withContext: myManagedObjectContext)
} catch let error as NSError {
    print(error)
}

Note that the answerthat @Bot linked to and that @JodyHagins mentioned has also been updated to this method.

请注意,@Bot 链接到的答案以及@JodyHagins 提到的答案也已更新为此方法。

回答by Bot

Really your only option is to remove them individually. I do this method with a ton of objects and it is pretty fast. Here is a way someone does it by only loading the managed object ID so it prevents any unnecessary overhead and makes it faster.

实际上,您唯一的选择是单独删除它们。我使用大量对象执行此方法,并且速度非常快。这是某人通过仅加载托管对象 ID 来执行此操作的一种方法,这样可以防止任何不必要的开销并使其更快。

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

核心数据:删除实体的所有实例的最快方法

回答by J2theC

Yes, it's reasonable to delete the persistent store and start from scratch. This happen fairly quick. What you can do is remove the persistent store (with the persistent store URL) from the persistent store coordinator, and then use the url of the persistent store to delete the database file from your directory folder. I did it using NSFileManager's removeItemAtURL.

是的,删除持久存储并从头开始是合理的。这发生得相当快。您可以做的是从持久存储协调器中删除持久存储(带有持久存储 URL),然后使用持久存储的 url 从您的目录文件夹中删除数据库文件。我使用 NSFileManager 的 removeItemAtURL 做到了。

Edit: one thing to consider: Make sure to disable/release the current NSManagedObjectContext instance, and to stop any other thread which might be doing something with a NSManagedObjectContext which is using the same persistent store. Your application will crash if a context tries to access the persistent store.

编辑:需要考虑的一件事:确保禁用/释放当前的 NSManagedObjectContext 实例,并停止可能正在使用相同持久存储的 NSManagedObjectContext 执行某些操作的任何其他线程。如果上下文尝试访问持久存储,您的应用程序将崩溃。