setInincludesSubentities:在 NSFetchRequest 中,跨多个持久存储的实体被破坏
现有技术并没有完全解决这个问题: 核心数据迁移错误消息“'模型不包含配置“XYZ”。'“
我已将其范围缩小到一个特定问题。不过,设置需要一分钟;请耐心听我说。
问题的要点是,持久存储协调器(显然)无法保留对象图的一部分,其中当托管对象存储在不同的文件中时,它们被标记为另一个对象的子实体。这里...
1) 我有 2 个 xcdatamodel 文件,每个文件包含一个实体。在运行时,当构建托管对象模型时,我使用 setSubentities: 手动将一个实体定义为另一个实体的子实体。这是因为尚不支持在编辑器中跨多个文件定义子实体。然后,我使用 modelByMergingModels 返回完整的模型。
//Works!
[mainEntity setSubentities:canvasEntities];
NSLog(@"confirm %@ is super for %@", [[[canvasEntities lastObject] superentity] name], [[canvasEntities lastObject] name]);
//Output: "confirm Note is super for Browser"
2)我修改了 persistenceStoreCoordinator 方法,以便它为每个实体设置不同的存储。从技术上讲,它使用配置,并且每个实体都定义了且仅一种配置。
//Also works!
for ( NSString *configName in [[HACanvasPluginManager shared].registeredCanvasTypes valueForKey:@"viewControllerClassName"] ) {
storeUrl = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:[configName stringByAppendingPathExtension:@"sqlite"]]];
//NSLog(@"entities for configuration '%@': %@", configName, [[[self managedObjectModel] entitiesForConfiguration:configName] valueForKey:@"name"]);
//Output: "entities for configuration 'HATextCanvasController': (Note)"
//Output: "entities for configuration 'HAWebCanvasController': (Browser)"
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:configName URL:storeUrl options:options error:&error])
//etc
3) 我为父实体设置了 fetchRequest,其中包含 setIncludesSubentities: 和 setAffectedStores: 只是为了确保我们同时涵盖 1) 和 2)。当插入任一实体的对象时,它们都会添加到上下文中,并且都由 fetchedResultsController 获取并按预期显示在 tableView 中。
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesSubentities:YES]; //NECESSARY to fetch all canvas types
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20]; // Set the batch size to a suitable number.
[fetchRequest setAffectedStores:[[managedObjectContext persistentStoreCoordinator] persistentStores]];
[fetchRequest setReturnsObjectsAsFaults:NO];
这是它开始出现错误的地方:关闭并重新启动应用程序后,仅获取父实体。
如果我使用 setEntity: 将请求的实体更改为“Note”实体,则会获取所有注释。如果我将其更改为“浏览器”实体,则会获取所有浏览器。让我重申一下,在第一次将对象插入上下文的运行过程中,它将出现在列表中。只有在保存并重新启动后,获取请求才能遍历层次结构。
因此,我只能断定是继承的存储有问题。让我们回顾一下原因:
- - Both entities can be created, inserted into the context, and viewed, so the model is working
- - Both entities can be fetched with a single request, so the inheritance is working
- - I can confirm that the files are being stored separately and objects are going into their appropriate stores, so saving is working
- - Launching the app with either entity set for the request works, so retrieval from the store is working
- - This also means that traversing different stores with the request is working
- - By using a single store instead of multiple, the problem goes away completely, so creating, storing, fetching, viewing etc is working correctly.
这只剩下一个罪魁祸首(在我看来):我使用 setSubentities: 设置的继承仅对会话期间创建的对象有效。
要么对象/实体被存储为剥离了继承信息,要么以编程方式定义的实体继承仅适用于新实例,或两者兼而有之。这些都是不可接受的。要么是一个错误,要么是我偏离了路线。
两天来我一直在以各种方式这样做;非常感谢任何见解。当前的解决方法 - 仅使用单个商店 - 完全有效,但如果我从应用程序等中删除其中一个模型,它就不会面向未来。它也令人难以置信,因为我不明白为什么你如果(setSubentities:)的核心定义不起作用,则将拥有所有这些基础设施,用于跨多个存储进行存储以及在获取请求中设置受影响的存储。
Prior art which doesn't quite address this:
Core Data Migration error message "'Model does not contain configuration 'XYZ'.'"
I have narrowed this down to a specific issue. It takes a minute to set up, though; please bear with me.
The gist of the issue is that a persistentStoreCoordinator (apparently) cannot preserve the part of an object graph where a managedObject is marked as a subentity of another when they are stored in different files. Here goes...
1) I have 2 xcdatamodel files, each containing a single entity. In runtime, when the managed object model is constructed, I manually define one entity as subentity of another using setSubentities:. This is because defining subentities across multiple files in the editor is not supported yet. I then return the complete model with modelByMergingModels.
//Works!
[mainEntity setSubentities:canvasEntities];
NSLog(@"confirm %@ is super for %@", [[[canvasEntities lastObject] superentity] name], [[canvasEntities lastObject] name]);
//Output: "confirm Note is super for Browser"
2) I have modified the persistentStoreCoordinator method so that it sets a different store for each entity. Technically, it uses configurations, and each entity has one and only one configuration defined.
//Also works!
for ( NSString *configName in [[HACanvasPluginManager shared].registeredCanvasTypes valueForKey:@"viewControllerClassName"] ) {
storeUrl = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:[configName stringByAppendingPathExtension:@"sqlite"]]];
//NSLog(@"entities for configuration '%@': %@", configName, [[[self managedObjectModel] entitiesForConfiguration:configName] valueForKey:@"name"]);
//Output: "entities for configuration 'HATextCanvasController': (Note)"
//Output: "entities for configuration 'HAWebCanvasController': (Browser)"
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:configName URL:storeUrl options:options error:&error])
//etc
3) I have a fetchRequest set for the parent entity, with setIncludesSubentities: and setAffectedStores: just to be sure we get both 1) and 2) covered. When inserting objects of either entity, they both are added to the context and they both are fetched by the fetchedResultsController and displayed in the tableView as expected.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesSubentities:YES]; //NECESSARY to fetch all canvas types
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20]; // Set the batch size to a suitable number.
[fetchRequest setAffectedStores:[[managedObjectContext persistentStoreCoordinator] persistentStores]];
[fetchRequest setReturnsObjectsAsFaults:NO];
Here is where it starts misbehaving: after closing and relaunching the app, ONLY THE PARENT ENTITY is fetched.
If I change the entity of the request using setEntity: to the entity for 'Note', all notes are fetched. If I change it to the entity for 'Browser', all the browsers are fetched. Let me reiterate that during the run in which an object is first inserted into the context, it will appear in the list. It is only after save and relaunch that a fetch request fails to traverse the hierarchy.
Therefore, I can only conclude that it is the storage of the inheritance that is the problem. Let's recap why:
- - Both entities can be created, inserted into the context, and viewed, so the model is working
- - Both entities can be fetched with a single request, so the inheritance is working
- - I can confirm that the files are being stored separately and objects are going into their appropriate stores, so saving is working
- - Launching the app with either entity set for the request works, so retrieval from the store is working
- - This also means that traversing different stores with the request is working
- - By using a single store instead of multiple, the problem goes away completely, so creating, storing, fetching, viewing etc is working correctly.
This leaves only one culprit (to my mind): the inheritance I'm setting with setSubentities: is effective only for objects creating during the session.
Either objects/entities are being stored stripped of the inheritance info, or entity inheritance as defined programmatically only applies to new instances, or both. Either of these is unacceptable. Either it's a bug or I am way, way off course.
I have been at this every which way for two days; any insight is greatly appreciated. The current workaround - just using a single store - works completely, except it won't be future-proof in the event that I remove one of the models from the app etc. It also boggles the mind because I can't see why you would have all this infrastructure for storing across multiple stores and for setting affected stores in fetch requests if it by core definition (of setSubentities:) doesn't work.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不幸的是,答案很简单。不支持跨物理上不同的文件和/或模型的子实体。在底层数据结构中,Core Data 将获取所有子实体并将它们展平为单个表。因此,如果您有一个 Parent、ChildA、ChildB 和 ChildC,每个都有 4 个属性,那么您最终将得到一个包含 16 列的表。
这就是为什么子实体不受您尝试的方式支持的原因。每个模型本身都是一个孤岛,最多只能对其他模型中的对象有弱引用。
更新
正如我上面所解释的,这是一个“设计不支持”的问题。由于底层数据结构中的持久化方式,您尝试执行的操作不受支持。创建子实体并不是您首先应该做的事情,因为它会扁平化您的数据模型。
实体继承不等于对象继承。您的对象可以按照您想要的任何方式、形状或形式进行继承。实体继承应该极其罕见,并且背后有一个非常的充分理由。
解决父子关系问题是使用实体继承的少数原因之一。
试图避免列重复不是一个好的理由。
由于您无法跨模型建立父/子关系(并且您确实拥有多个模型,即使它们被合并到
NSManagedObjectModel
的一个实例中),因此不太可能这是跨模型进行实体继承的一个原因,而无法通过类似的解决方案来解决。The answer is unfortunately easy. Subentities across physically different files and/or models is not supported. In the underlying data structure, Core Data will take all sub-entities and flatten them out to a single table. So if you have a Parent, ChildA, ChildB and ChildC each with 4 attributes, then you will end up with a single table with 16 columns.
This is why sub-entities are not supported in the manner that you are attempting. Each model is a silo unto itself and can at most have weak references to objects in the other model(s).
Update
It is an issue of "not supported by design" as I explained above. What you are trying to do is not supported because of the way it is persisted in the underlying data structure. Creating sub-entities is not something you should be doing a lot of in the first place because it flattens your data model.
Entity inheritance does not equal object inheritance. Your objects can inherit in any way, shape or form you want. Entity inheritance should be extremely rare and have a very good reason behind it.
Solving parent child relationship issues is one of the few reasons to use entity inheritance.
Trying to avoid duplication of columns is not a good reason.
Since you cannot do parent/child relationships across models (and you do have more than one model even if they are being merged into one instance of the
NSManagedObjectModel
) it is unlikely to be a reason to do entity inheritance across models that can't be solved via a similar solution.@SG
你问:
您可以拥有多个模型并将它们保存到不同的持久存储,因为您想处理 xcdatamodel 的不同部分,例如编辑 xcdatamodel 的属性EntityA 影响与 EntityB 的关系,同时重新排序 EntityB 的大量托管对象。
您还可以在读取新对象时在另一个线程中执行更好的 I/O 流,完成这项工作后,您可以将它们与模型的现有数据“合并”。
@SG
You ask:
You can have multiple models and save them to different persistent stores because you would like to work on different parts of your xcdatamodel, e.g. editing attributes of EntityA effecting relationships to EntityB while reordering a huge amount of managedObjects of EntityB.
You could also perform better I/O streaming in another thread while reading in new objects, after this work is done, you could 'merge' them with existing data of your model.