Core-Data: fetching object count from MOC after saving - core-data

Im currently having a problem with my CoreData implementation. I try to query the number of objects with certain properties. I therefore execute the following code
NSUInteger itemsCount = [managedObjectContext countForFetchRequest:fetchRequest error:NULL];
NSLog(#"%i",(int)itemsCount);
So far so good. Everything works if i work with a fresh MOC where no data has ever been saved. The problem occures when i do the following:
I save the whole MOC
Delete one of those objects matching the fetch criteria from the MOC (without saving!)
Requery the Number of objects matching the criteria
The number of objects does not change although i deleted one of the objects.
I also tried the following query ...
fetchRequest.resultType = NSCountResultType;
NSArray* array = [managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
NSLog(#"%i",[[array firstObject] intValue]);
... with the same result.
Then I tried querying the objects directly ...
fetchRequest.resultType = NSManagedObjectResultType;
array = [managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
NSLog(#"%i",(int)[array count]);
... tada: Here it works. The array does not contain the deleted object.
When I set fetchRequest.includesPendingChanges = NO, i of course get all objects because i did not save yet. The property has no effect ob the first two queries though because they show too many objects in the first place.
So my question is, what am i doing wrong and why can i not obtain the right amount of objects once i saved them to the persistence store. Do i have to reset the MOC after saving?
--------------------------------------------------------------------------------
Update
I searched further for similar problems and found some interesting posts. Fetching objects from the MOC means also querying the persistence store. Since I did not save my changes immediately, I do or don't fetch deleted and newly inserted objects respectively. But strangely this only applies for queries involving dictionary result types or count fetches. When querying the objects itself i retrieve the correct number of objects including newly inserted and without the deleted ones.
Now I am wondering on how to deal with my problem. I have a table view which smoothly works with a NSFetchedResultsController (NSFRC). I do not have to save any changes since the controller handles all the updates and notifies me. But as soon as i insert or delete data and the NSFRC notifies me, i want to do further calculations to update my other views. Those calculations include average and number of objects with certain attribute etc.
Right now I update those view as soon as an insert or delete delegate method is being called. But those queries wont return the correct values.
Calling the following before querying has no effect on those queries.
[moc processPendingChanges]
fetch.includePendingChanges = YES
Apparently my approach is lacking something. How do i fetch my needed values correctly ? Since my NSFRC obviously keeps track of deleted and inserted values im curious if it would help to create a second NSFRC just to fetch the averages and so on? Is it even capable of that?
Thanks!

Related

CoreData: How to refresh "calculated" attributes?

My NSFetchedResultsController work great, as long as only "basic" attributes get changed. However if I have a label which is calculated and I'm changing some attributes influencing this label in another view controller on the navigation controller stack, this label doesn't get updated.
For example my label should show the amount of a budget position left saved in the entity SpendingCategory.
self.budgetLeftLabel.text = [NSString stringWithFormat:#"%# %#", [[self.spendingCategory getExpendituresAmount] getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(#"left", nil)];
I derive this value from the category on SpendingCategory with this method:
- (NSNumber *)getExpendituresAmount
{
return [self.hasExpenditures valueForKeyPath:#"#sum.amount"];
}
However this label doesn't get any updates by the NSFetchedResultsController. And I have several locations in my app where this doesn't happen because a value is calculated. What do I need to change that these updates happen?
EDIT with datastructure:
Ok my Spending Category datastructure is roughly (for budget):
name (string)
cost (double)
position (integer 16)
Relationsships: hasExpenditures
My Expenditures structure (for tracking):
amount (double)
date (Date)
description (string)
Relationsships: forSpendingCategory
I hope it's clearer now. So why do these values not get updated?
The NSFetchedResultsController gets tickled when attributes in the relevant NSManagedObject instances are updated. If you are changing something that is purely calculated then the update never fires. Why is this relevant?
If you are changing something in the Expenditures entity (btw, entities should be singular in name) and you are watching the Spending Category entity then the NSFetchedResultsController won't fire because you didn't change anything that is relevant.
How to fix this?
Depends. I normally keep that derived value in the entity and persist it. Further, whenever a child changes a relevant value, I have the parent recalculate. This will cause the NSFetchedResultsController to fire.
How do you watch the values?
Either you have the child call a method on the parent (icky) or you have the parent watch the values on its children via KVO (better). Your personal preference decides here.
Update 1
To keep the derived value in the entity you add a new attributed to the entity and store it. Nothing is special about the attribute. It helps to keep in mind that Core Data is not a database. Core Data is your data model that happens to persist to a database if you so choose. Therefore you want to denormalize the database in cases like this.
while I was searching SO to find a good link for watching children, I stumbled across this example.
KVO object properties within to-many relationship
While the accepted answer is not very good, the second answer, using a NSFetchedResultsController is quite interesting and is worth exploring. The basic idea is that your parent objects instantiate a NSFetchedResultsController on -awakeFromFetch or -awakeFromInsert and when it fires, they recalculate the derived value. Thus the value is always up to date and your view controller based NSFetchedResultController instances will fire because the parent object has changed.
I did something similiar time ago, basically you need to store your calculated value in a transient attribute in your CoreData model, rather than implement your own setter and getter. Then in the related NSManagedObject you need to implement two methods:
// this will populate the values when
// the entity is retrieved from the store
-(void)awakeFromFetch {
[self refreshCellInfo];
}
// this will refresh the values when
// the object goes to fault
// (for example when it is off screen)
-(void)willTurnIntoFault {
[self refreshCellInfo];
}
-(void)refreshCellInfo {
// update all your derived values...
}

is there a way to see which objects in a managedobjectcontext have not yet been added to the persistent store?

is there a simple and efficient/fast way to query a managedobjectcontext to get an array of all the managedobjects in the context that have not yet been added to the persistent store?
i ask this because i would like to be able to save nsmanagedobjects that have been added to the MOC only if they conform to certain criteria. basically i want to be able to do this so that if some unexpected event happened before my managed object attributes were properly populated, i can catch this fact and purge the object(s) before saving the context. given the complexity of the navigation possible in the app, i'd like to have a look at the data to be sure they are good before i save.
i suppose i could also do this with some kind of validation rule and a flag field that doesn't get set until i am sure the user has added all the data to the record, but i don't yet know how to implement this...
any help much appreciated.
The insertedObjects method of NSManagedObjectContext
returns the set of objects that have been inserted into the context but not yet saved in a persistent store.

RestKit/CoreData not updating - creating duplicates

I have an ios 5 app which does not create any data - it simply makes a GET call to a REST webservice and populates the sqlite database with those records. The initial GET works great when there are no records in the local database. However when I make subsequent calls, I will only be returning a subset of records whose data has changed since the last GET. But what is happening is that the records are just being added again, not updating the existing records.
I have an ID field which is the primary key (or should be) and when a record comes in whose ID already exists, I want that data to be updated. If that ID does not exist, it should be an insert.
I didn't see a way to set my ID field as a 'primary key' in the datamodel in XCode. I tried doing this in my didFinishLaunchingWIthOptions method:
userMapping.primaryKeyAttribute = #"id";
But that alone didn't really seem to do anything.
This is my call to actually perform the GET:
// Load the object model via RestKit
[objectManager loadObjectsAtResourcePath:[#"/synchContacts" appendQueryParams:params] delegate:self];
Which seems to do everything automagically. I am lost at this point as to where I should be putting logic to check to see if the ID exists, and if so do an update vs an insert, or what.
As of the latest RESTKit version (0.23) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.
You seem to be doing it correctly and when your didLoadObjects callback happens you should be able to query Core Data for the objects you need.
You might be having an issue with the way your fetch requests are being set up. With the latest RestKit you can use RKObjectMappingProvider's
- (void)setObjectMapping:(RKObjectMappingDefinition *)objectMapping forResourcePathPattern:(NSString *)resourcePathPattern withFetchRequestBlock:(RKObjectMappingProviderFetchRequestBlock)fetchRequestBlock;
function and have the fetchRequestBlock fetch the proper data.
RestKit doesn't really handle partial update requests very well out of the box though. You might have more luck on the RestKit google group which is very active.
Quote:
I didn't see a way to set my ID field as a 'primary key' in the datamodel in XCode. I tried doing this in my didFinishLaunchingWIthOptions method:
userMapping.primaryKeyAttribute = #"id";
Keep in mind, the 'primaryKeyAttribute' is the one from your api payload, NOT a CoreData id, which CoreData manages on its own. RestKIt then maps the (invisible) CoreData primary key to the specified JSON key.

Restore one fetched entity out of many -- Core Data

This question covncerns my lack of understanding of how to use the core data undo manager and how to restore a NSManagedObject to its state before editing was done.
I am just learning my way around Core Data. I have my NSManagedObject classes set up with their dynamic accessors. I perform a fetch that returns several NSManagedObject entity results. Content from each of these entity results (first name, last name) get put into a table view, and then the user picks one out of the table for detailed view and then editing.
The detail view controller receives a pointer to the selected NSManagedObject entity. As the user edits the fields, the corresponding property value in the NSManagedObject entity is updated. This seemed like the cleanest way to manage these changes.
Now, rather than committing the changes using save, I want to provide a cancel-editing feature that rolls back to what is in the data base for that entity. I really only want to restore the one entity and not perform the entire refetch.
I tried rollback and I tried NSUndoManager (with beginUndoGrouping and endUndoGrouping), and that is not working. I don't think I understand what rollback is really supposed to do.
But in any case, I still want to restore the property values in just that single entity (taking the lazy approach to only fetch what is needed, which is the one entity) so that my detail view controller can refill its view with the correct information. Right now it is using the NSManagedObject entity values, which contain the edited values, which were cancelled.
I suppose I could just start the edit process by creating a copy of the NSManagedObject. If the cancel-editing button is pressed, I could copy it back into the original. (I might even be able to just replace the original with the copy by moving the pointer. But since the pointer has actually been passed through several objects, I'm not sure how to manage the retain number on the copy.)
Does anyone have any other suggestions?
Thanks
Using rollback should accomplish what you want and I'm not sure what it doesn't. It is probably an implementation detail error.
You can find the specific managed object/s that were updated but not yet saved by calling the context's updatedObjects.

Multiple layers of MOC merges...CoreData veterans, what do you think?

Original question has been answered. Update addresses related question raised in comments.
Original post:
I am using the MOC save method used in Apple's CoreDataBooks. However, I seem to have use for two layers of MOC merging (three MOCs where the 3rd merges with the 2nd and then the 2nd merges with 1st).
First, I have a tableview (ClassList) listing school classes. Selecting a class pushes a 2-row tableview (AddClass). At AddClass, the first row allows the user to edit the class title. Selecting the second row pushes a tableview (ClassRoster) that displays the student roster for that class. Lastly, selecting a student pushes on another 2-row tableview (AddStudent) where the user can edit the student name and username.
I can add and save classes successfully by using the dual MOC merge method (managedObjectContext and addingManagedObjectContext as employed by CoreDataBooks). I will call the "base MOC" in my first view "MOC1" and call the "scratchpad" MOC "MOC2".
MOC2 temporarily stores changes made to a class object. These changes can then either be saved or canceled, sending a -didFinishWithSave:(BOOL) to the delegate. If I save, the changes made in MOC2 are merged with MOC1. That merge is working perfectly.
Handling changes made to student objects is where I'm going wrong. I thought I could employ MOC3 as a scratchpad for changes to student objects which would merge with MOC2 (when I saved a student object). MOC2 could in turn be saved with MOC1 when I saved the class object.
But I have run into errors with saving MOC3 and adding student objects to class objects because they are in different contexts. I can post code, but first I wanted to ask the bigger question: Am I going about this all the wrong way?
UPDATE:
Mr. Zarra recommended using initWithEntity:insertIntoManagedObjectContext: and setting the MOC to nil, thereby creating a temporary object which could later have its MOC set and saved.
Following his advice, I am attempting to incorporate the following code:
NSManagedObjectModel *managedObjectModel - [[managedObjectContext persistentStoreCoordinator] managedObjectModel];
NSEntityDescription *entity = [[managedObjectModel entitiesByName] objectForKey:#"MyClass"];
MyClass *newClass = [[MyClass alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
I have run into an error, but I'm not sure it is related to this code yet. I will debug and post what I find.
Yes. You do not need to use more than one NSManagedObjectContext. That example is a very poor one. In your case you should be using a single context and that will remove all of your issues.
If you want a temporary entity, create it with a nil NSManagedObjectContext. When you want to save it you call -setManagedObjectContext: and then save that NSManagedObjectContext.
The only time you realistically want to use more than one NSManagedObjectContext is when you are in a multi-threaded situation.

Resources