setIncludesSubentities: in an NSFetchRequest is broken for entities across multiple persistent stores
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.
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
You ask:
Why are there all these hooks for reading in multiple models, and saving to multiple persistent stores...
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.
精彩评论