Core Data applying delete rule when updating - ios

Beginner in Core Data here, there is something very basic in Core Data that I don't understand. I know the delete rules, such that if my object gets deleted, if it has relationships that are cascade for example, those relationships will get deleted as well. But what happens on updates?
Example:
Person has a relationship to a car. Delete rule is cascade.
Person --> Car
If Person is deleted, Car will be gone too.
But now what if Person just points to another Car, the previous Car will NOT be deleted and will just be dangling in the DB.
Any solutions to this?
I figured ideally you should delete the first car before setting the new one, but this is automatically done from a server fetch.

If this is the behavior you want in all cases you could override the managed object subclass method to set the new relationship. In the method, check first if another object exists and delete it if desired.
E.g.
-(void) setCar:(Car *)car {
if (Car* oldCar = self.car) {
[self.managedObjectContext deleteObject:oldCar];
}
[self willChangeValueForKey:#"car"];
[self setPrimitiveValue:car forKey:#"car"];
[self didChangeValueForKey:#"car"];
}

Related

Core Data does not update inverse relationship

My two entities are Book and Category. A book can have many categories, and a category can have many books. The relationships are marked as the inverse of each other in the data model.
When I update the categories of a book I expected that the books of the corresponding categories would be updated automatically. In other words:
Book *book = [Book insertNewObjectIntoContext:context];
Category *category = [Category insertNewObjectIntoContext:context];
[book addCategories:[NSSet setWithObject:category]];
BOOL result = [category.books containsObject:book]; // Should be YES
Yet, I get NO in my app. books returns an empty set.
I triple-checked that the relationships are marked as inverse. What else could be at play here? Or is there something I'm missing?
Would it hurt if I set the inverse relation manually? If I do everything works like a charm.
The inverse relationship is not updated until the end of the run loop. Calling it immediately after like that does not give it time to update.
If you were to do something like:
dispatch_async(dispatch_get_main_queue(), ^{
BOOL result = [category.books contains:book];
NSLog(#"Result: %#", result ? #"YES" : #"NO");
});
You would see your expected result.
Update
It does not hurt to set the reverse manually. When everything is working correctly it results in a waste of cycles because Core Data has to double check the relationships so you will see a performance hit.
You can call
[context processPendingChanges];
to force the managed object context to update the inverse relationships immediately.
This method is automatically called during the event loop, or when you save the
context. But you can call it explicitly as well. From the documentation:
Forces the receiver to process changes to the object graph.
...
You can also invoke it manually to coalesce any pending unprocessed
changes.

What's the point of self.managedObjectContext == nil in NSManagedObject prepareForDeletion?

I have a Reminder entity that needs to update its date property whenever a certain entity B is deleted. I've spent some days coding thinking I could do some useful things in my managed object subclass on deletion time. I tried
- (void)willSave
{
if (self.isDeleted)
// use self.managedObjectContext
}
The context was nil. Relationships were also torn down there. Fair enough.
So... I started writing cumbersome code for prepareForDeletion to circumvent the fact that the object hadn't been deleted yet, but then Core Data throws self.managedObjectContext == nil in my face. The documentation says that this is where I do stuff "before relationships are torn down". So what is the point in self.managedObjectContext == nil if self.relationshipA.managedObjectContext is accessible (as the docs suggest)? And more importantly, why does my not yet deleted object not have its context?
I read a comment here regarding that problem
its not 'fault' as much as it is a 'disown', the context has disowned your object (he was deleted and save was committed to the database) and so your object was disowned. don't save in methods that are changing and object as the save should probably be committed/saved after the operation anyway. – Dan Shelly May 21 at 19:05
My code was:
[moc deleteObject:obj]
[moc save:NULL]
When I removed the save operation my self.managedObjectContext existed in prepareForDeletion. That is, until auto-save, when it was nil again. Probably because the parent context also deleted it, followed by a save by the UIManagedDocument.
I'm starting to think that my only options are to make a custom delete method (that works until Core Data cascades a deletion, in which case it won't be called), or make a new class that listens to NSManagedObjectContextDidSaveNotification.
Update:
The user wants to keep in touch with a person, and wants to be reminded after a certain interval (stored in ContactWish) if no contact has been made. What I'm trying to accomplish is that when the latest ContactOccasion for a certain person is deleted, the corresponding occasion->person->wish->reminder gets updated (using the interval).
Since this is a learning experience for me I wanted to find out the right way (one that works with cascade deletion etc.) and not just call for an update manually from every place in my code where I do [MOContext deleteObject:occasion]. Suggestions are welcome.
(the reminder entity has also been prepared for more manual use)
Would it not be much more logical to have the Reminder entity manage its date property? It could "listen" (maybe via changedValues:) to its relationship entities being deleted and perform the update.
This seems more consistent, as the B entity should not really be concerned with the logic of the Reminder entity updates.
Edit
Pursuant to the discussion below and based on my opinion that you cannot load up the database cascade delete model too much with update logic:
Rather than react to a deletion you can introduce an attribute that you set and listen to in order to do the changes.
I really do not see how relying on core data delete mechanisms is easier or more elegant than just writing your own "deleteOccasion" method that handles this logic.

Deleting or removing ManagedObject in CoreData

In the documentation and in the broad literature the generated factory method to delete/remove a subclassed managed object in CoreData for IOS is
(void)removeXXXObject:(NSManagedObject *)value
where XXX is the corresponding relationship or we can use simply removeObject.
In my code I used this:
Data *lastData = [[self sortedPersonDatas] objectAtIndex:0];
[selectedPerson removePersonDatasObject:lastData];
where PersonDatas is a one-to-many relationship to Data managed object from I took the last data (lastData resulted from a sorted array of all data)
But using the first two remove methods and checking the SQL database behind we can find that the actual data is existing just the inverse relationship is null.
To completely delete the data (all attributes and the object) I had to use:
[selectedPerson.managedObjectContext deleteObject:lastData];
The question: which is the better method and is it correct that CoreData leaves the data intact?
removeXXXObject only removes an object from a to-many relationship, but does not delete the object from the store. To do so, you have to indeed use deleteObject - this is the desired behavior. Calling deleteObject will by default also set the corresponding relationships to nil (see https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW1).

How can I save an object that's related to another object in core data?

I'm having difficulty with a one to one relationship. At the highest level, I have a one to many relationship. I'll use the typical manager, employee, example to explain what I'm trying to do. And to take it a step further, I'm trying to add a one to one House relationship to the employe.
I have the employees being added no problem with the addEmployeesToManagereObject method that was created for me when I subclassed NSManagedObject. When I select an Employee in my table view, I set the currentEmployee of type Employee - which is declared in my .h.
Now that I have that current employee I would like to save the Houses entities attributes in relation to the current employee.
The part that I'm really struggling with is setting the managedObjectContext and setting and saving the houses attributes in relation to the currentEmployee.
I've tried several things but here's my last attempt:
NOTE: employeeToHouse is a property of type House that was created for
me when I subclassed NSManagedObject
House *theHouse = [NSEntityDescription
insertNewObjectForEntityForName:#"House"
inManagedObjectContext:self.managedObjectContext];
// This is where I'm lost, trying to set the House
// object through the employeeToHouse relationship
self.currentEmployee.employeeToHouse
How can I access and save the houses attributes to the currentEmployee?
since House is setup as an Entity it can be considered a table within the data store. If that truly is the case, you need to setup a 1 to 1 relationship between Employee and House in your data model.
If you have already done so, then it is as simple as calling. Although I'm not as familiar with one to one relationships with Core Data as I am with to-many. In either case, try one of the following
[self.currentEmployee addHouseObject: theHouse];
or
self.currentEmployee.employeeToHouse=theHouse;
then to the save to the managedObjectContext:
NSError *error=nil;
if (![self.managedObjectContext save:&error]{
NSLog(#"Core Data Save Error: %#", error);
}
Also, I'm not sure about your particular situation, but your self.managedObjectContext should already be the same as the one pointed to by self.currentEmployee.managedObjectContext.
Good luck,
Tim

CoreDataGeneratedAccessors to remove object don't seem to be deleting

I have an NSManagedObject that has a to-many relationship to another NSManagedObject.
During creation of the NSManagedObject I can use the generated accessors 'removeNotesObject' and the deletion works fine. I can create an object to add to the parent object, save the object, delete the object and then save again. When I fetch this parent object the object I created and deleted is still deleted.
However, after I add the object and then save it (but don't delete and save after) and then fetch it, I can't seem to delete the object that was previously created. I am using the generated accessors to try and remove the object, which appears to work but when I fetch it again the object hasn't been deleted.
(Note: Adding objects does work so it is not a problem with the saving)
To delete the object I retrieve the set of object and select the objects I want to delete. Then I remove the objects
NSSet *notes = summary.notes;
NSSet *oldNotes = [notes objectsPassingTest:^(id obj,BOOL *stop){
Note *oldNote = (Note *)obj;
BOOL sameRow = (oldNote.row == newNote.row);
BOOL sameColumn = (oldNote.column == newNote.column);
BOOL success = (sameRow && sameColumn);
return success;}];
[summary removeNotes:oldNotes];
I have tried making the relationship inverse to delete the objects which didn't delete them. I have also tried different delete rules (cascade and nullify) which again didn't work. Finally, I tried to remove each object separately and deleting each object from the context after I had removed it from the parent object which again unfortunately didn't work.
I assume the problem must be something to do with it being a fetched object. If anyone could help I would really appreciate it as I can't think of any other ways to test or solve this problem.
You need to do
NSManagedObjectContext * moc = .......;
[moc deleteObject:note]
edit: The core data generated accessors simply remove the object from the relationship, but do not delete the object permanently. This makes sense because you may have one NSManagedObject associated to multiple other NSManagedObjects via relationships.
edit: Deleting in the above mentioned fashion will invoke the deletion rules. I suggest you double check that they are setup correctly.
The reason the above code did not work is that == will not actually compare the NSNumber. Instead you need to call 'isEqualTo:'. I think before it was checking the address hence working before I saved it. What's more it was returning an object in the NSSet so appeared to be working. During debugging it wasn't clear what the object was but clearly wasn't the one I needed.

Resources