Deleteing a managed object from an entity - cocoa

I've got a managedObjectContext with 2 entities, each of which contains multiple entries. Objects of Entity A are represented in a Table View, and I want the user to be able to delete any entity is Entity A. My problem is that, when I send the request to delete that entry, the wrong entity is being called!
FYI, I’m handling the deleting process in a separate method, so when the delete button in Table view is triggered, before it’s been taken out of the view, I first want to make sure it’s been removed from the managedObjectContext.
- (BOOL) deleteCompletedSuccessfully : (EntityA *) anEntry
{
[self.managedObjectContext deleteObject: anEntry];
NSError *error = nil;
If (![self.managedObjectContext save:&error])
{
NSLog (#”%#”, [error userInfo]);
return NO;
}
return YES;
}
The error is:
Error Domain=NSCocoaErrorDomain Code=1570 \"The operation couldn\U2019t be completed. (Cocoa error 1570.)…. And the rest of the error message indicates that I’m trying to delete a nil object in EntityB!!!! While anEntry is in fact in EntityA.
I tried encapsulating the input (anEntry is this case) into an array, i.e:
- (BOOL) deleteCompletedSuccessfully : (NSArray *) array
{
EntryA *anEntry = [array objectAtIndex: 0];
// and the rest of the code
The same error. How can I make it look for that particular entry in a particular entity?!
Any help?

I think you've got some conceptual confusion going on here between entities and managed objects.
Entities are abstractions analogous to Classes. Managed objects are actual individual instances of NSManagedObject or one of its subclasses. Entities in the data model tell the managed object context what attributes and relationships to one another the managed object instances will have.
Entities exist solely in the data model whereas Managed objects are in the "object graph" which is the actual functioning group of related objects alive in memory. Entities simply describe to the managed object context how everything fits together much as a class definition tells the compiler how all the properties and behaviors of fit together in a class. Managed objects as instances have data and behaviors just like all other live objects.
Likewise, a managed object context does not add, remove or set the values of entities in any way. Instead, it adds, removes or set the values of managed objects configured by the entities in it's data model.
So, when you say:
I've got a managedObjectContext with 2
entities, each of which contains
multiple entities.
What you really mean is:
I've got a data model with 2 entities and a managed object context with many managed objects configured by those entities.
A tableview may display only the data from the instances configured to one entity (that is most common) but the actual data and the insertions and deletion happen to managed object instances and not the entities which are unalterable by that point.
However, I don't think the terminology confusion is the actual cause of your problem. Instead, I think the error is trying to tell you that you are deleting an object configured by EntityA from a required relationship with a an object configured with entityB.
The cocoa error 1570 is a NSValidationMissingMandatoryPropertyError which as the name suggest occurs when you try to save a managed object that has a required property with a nil value. The manage object context tries to validate the object graph before it saves and when it finds a missing required property it throws that error.
I can't tell you anything more because I have no idea what your data model looks like.

Related

Two NSArrayControllers on one NSManagedObjectContext?

I have two NSArrayControllers in my MainWindow.xib and want to use both of them with CoreData. Both are using entities of the same type from the same data model. These two array controllers are bound each to their own table views (one for generated data, one for stored data).
The first array controller is connected to the managed object context in AppDelegate but what do I do about the second array controller? It doesn't seem it can be connected to the same managed object context. If I compile now I get the error Cannot perform operation without a managed object context for the second array controller.
To answer this question on my own now:
The binding inspector in IB has bindings on NSArrayController for the Managed Object Context. I wasn't completely sure that this is the right way to bind the array controllers but it obviously seems to be so.

Having an NSSet as part of an NSManagedObject in Core Data

I have a class which as its properties has a number of NSSet values. The content of these sets is not stored in Core Data, but comes from another source. They are basically ID strings which are unique. No point in filling up Core Data with them as separate entities.
However, as far as I can see it's not possible to store an NSSet as an attribute of an NSManagedObject. I guess I would need to serialise it into binary data by hand, whenever the object gets stored, and deserialise it when it gets retrieved from the persistent store? The same would also apply to storing other collection classes.
Has anybody else ever come across this issue and is able to give some advice?
You can set the attribute type to 'transformable' and then directly store the set into the attribute. Be sure that all of the contents of the set conform to < NSCoding >.
In Xcode, in the Project Navigator open up the project_name.xcdatamodeld source for the model editor and select the entity in question. Choose Editor in the Xcode menu bar and there is a Create NSManagedObject subclass... item. Make the subclass and use it for extra non-Core Data properties. The subclass becomes part of the model in place of the entity. It retains all of the entity's behaviors.

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.

Adding custom methods to a subclassed NSManagedObject

I have a Core Data model where I have an entity A, which is an abstract. Entities B, C, and D inherit from entity A. There are several properties defined in entity A which are used by B, C, and D.
I would like to leverage this inheritance in my model code. In addition to properties, I am wondering if I can add methods to entity A, which are implemented in it's sub-entities.
For example:
I add a method to the interface for entity A which returns a value and takes one argument
I add implementations of this method to A, B, C, D
Then, I call executeFetchRequest: to retrieve all instances of B
I call the method on the objects retrieved, which should call the implementation of the method contained in B's implementation
I have tried this, but when calling the method, I receive:
[NSManagedObject methodName:]:
unrecognized selector sent to instance
I presume this is because the objects returned by executeFetchRequest: are proxy objects of some sort.
Is there any way to leverage inheritance using subclassed NSManagedObjects?
I would really like to be able to do this, otherwise my model code would be responsible for determining what type of NSManagedObject it's dealing with and perform special logic according to the type, which is undesirable.
Any help is appreciated, thanks in advance.
It should work. The objects returned by executeFetchRequest: are real instances of NSManagedObjects (or subclasses thereof.)
The steps to use custom classes in CoreData are as follows. Say you have entities A and B, where B inherits from A.
Then you need two custom classes as
#interface A:NSManagedObject{
}
-(void)someMethod:(NSString*)a;
#end;
#interface B:A{
}
-(void)someMethod:(NSString*)a;
#end;
Then set them in the XCode data modeler as shown:
This way, the CoreData automatically assigns the correct class to the NSManagedObject when it is fetched from the database.
If you're getting that exception, it means Core Data is not using your custom class. The key here is NSManagedObject -- that's the object Core Data created for the objects in your data store.
If you haven't already, you'll need to create a class that inherits from NSManagedObject, add your custom methods there, and then set entity A to use your custom class in the object model tool. If entities B, C, D, etc. have specific behaviors, you should subclass the class you created for entity A and assign those entities to use the subclasses too.
Essentially, you have a parallel hierarchy: one hierarchy of entities, and another of classes. You'll likely end up with entity X and class X for each entity in your object model.
After trying lots of solution calling isMemberOfClass on my NSManagedObject subclass before trying to use my custom method made the trick.
[thing isMemberOfClass:[Thing class]];
[thing customMethod]; //was getting unrecognized selector sent to instance here before
I had this same error for the same underlying reason, but it came about in a different situation and a different cure. Your suggestion helped me a lot!
Originally I had created my class implementing my entry by hand. I didn't know there was an Xcode menu for this. I think the link was never there! So it wasn't until I had added and began testing the new custom methods (not setter/getters) that I started to get the error.
My solution was to change the name of my class, have Xcode re-create the class for my entry via Editor->Create NS Mangage Object.... Then cut and paste in the old code into the new class. No difference in code!
Xcode seems to have some kind of internal link that is not apparent in the code.

Can a NSManagedObject own another as a property?

I've taught myself Obj-C, and have been self-teaching Cocoa, but adding Core Data to my program has given me nothing but a huge headache, thanks to me needing extensive custom logic. Here are a couple of the questions that are driving me insane.
What if I want a Managed Object to own another Managed Object? It seems if I give it a to-many relationship, the owned object will simply be shared by various masters, but I want each Owner to have its own.
If I subclass an NSManagedObject, can I make simple calls to the Array Controller to remove or copy instances of ManagedObject, and assume those will be translated into the Core Data model?
If I want to programmatically edit the properties of a ManagedObject, can I get away with mere KVC calls to the Array Controller? What's all this talk about NSPredicate an NSFetchRequest to the NSManagedObjectContext from the NSManagedDataStoreDrivingMeCrazy? Can I make an NSFetchRequest that filters the relationships of an object currently selected in a table view?
Once I use a fetch request to get a group of objects, how do I go about querying their relations? Does that require a whole other FetchRequest, Predicate, and so forth? Isn't Core Data supposed to be easier? Am I missing something?
An entity is similar to a class--it's a blueprint for a managed object that will be instantiated later. Each managed object will have its own attributes and relationships to configure.
You can definitely insert and delete managed objects. You might have to do some code to support copying, but I am not sure.
Yes, the properties (attributes and relationships) of managed objects support KVC (and KVO and bindings).
You can access the object or set of objects simply by using the relationship name that you define in the model (no additional fetch or logic is required).

Resources