Traversing multiple Core Data objects using NSPredicate - objective-c

I am having a problem with a Core Data model in Cocoa. It's probably a fairly basic problem. A section of my Core Data model is shown below. Given the value of a cell property in the OutputCell entity, I want to return the relevant HistogramBar.
I'm using the following Predicate but it just returns an empty array. I've managed to get it working using the Histogram entity but I don't seem to be able to traverse from HistogramBar through Histogram and on to OuputCell. The predicate I'm using is:
NSEntityDescription *histogramBarEntityDescription = [NSEntityDescription entityForName:#"HistogramBar"
inManagedObjectContext:[theDocument managedObjectContext]];
NSFetchRequest *histogramBarRequest = [[[NSFetchRequest alloc] init] autorelease];
[histogramBarRequest setEntity:histogramBarEntityDescription];
NSPredicate *histogramBarPredicate = [NSPredicate predicateWithFormat:#"(histogram.outputCell.cell = %#)", theOutputCell];
[histogramBarRequest setPredicate:histogramBarPredicate];
NSError *histogramBarError = nil;
NSArray *histogramsArray = [[theDocument managedObjectContext] executeFetchRequest:histogramBarRequest
error:&histogramBarError];
Thankyou for the help.

My problem is solved, a rogue comment in the code prevented the HistogramBar entities being created. In this instance detailed checking of some NSLog's helped spot the problem.

Related

Keep getting "'executeFetchRequest:error: A fetch request must have an entity.'" Even though I clearly set an entity :\

And yes, I've set up all the proper entities in my xcdatamodelId. I've been using CoreData for months now and have never run into this problem just yet. Is there something else I'm possible missing?
This is my code to pull data.
- (NSArray *)getCDRuneSets {
NSError *error;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Rune" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *cdRuneSets = [context executeFetchRequest:fetchRequest error:&error];
return cdRuneSets;
}
And I have an entity named "Rune" in my xcdatamodelId. Any ideas on what I could possibly be doing wrong? :\
Edit:
I also have my AppDelegate run this piece of code to instantiate the my CoreDataBank's (It's a StaticSingleton) context
CoreDataBank *bank = [CoreDataBank getBank];
[bank setContext:[self managedObjectContext]];
Another possible solution to this incase anyone finds it... Make sure your entity name in your model matches your specified entity in entityForName
I had an s in my model (as in plural) and not in my code :(

NSArray count returns correct number but faults with data access

I am attempting to get a count of items in an array to place it in the right detail of a cell. Interestingly enough I am getting the proper count but my when I try to NSLog the array it returns <fault>.
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:#"muscleGroup == %# && specificExercise == %# && date >= %# && date < %#", theMuscleGroup, theExercise, fromDate, toDate];
NSFetchRequest *fetchRequestTemp = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"WeightLiftingInfo" inManagedObjectContext:_managedObjectContext];
[fetchRequestTemp setPredicate:searchPredicate];
[fetchRequestTemp setEntity:entity];
NSError *error;
NSArray *tempArray = [[NSArray alloc] initWithArray:[_managedObjectContext executeFetchRequest:fetchRequestTemp error:&error]];
NSLog(#"%#", tempArray);
NSLog(#"%d", [tempArray count]);
My Question:
Basically, what am I doing wrong? Am i initing the array improperly or trying to log it improperly?
I have been researching for hours and trying different coding, but cannot figure it out. Any help would be appreciated. If you need more info or I am doing something against guidelines according to SO please let me know before you close this thread or "-1". HAVE MERCY, I am new and leaning:)
When you execute your fetch request, you get back a managed object fault. That is, a proxy-like object that represents the results even though the real data objects haven't been loaded from the data store yet. The fault knows how many objects are in the array, etc., and when you access the objects the fault will transparently load the real objects.
So, situation normal, don't worry about it. Try using the results instead of logging them and I think you'll find that things work fine.

NSSortdescriptor ineffective on fetch result from NSManagedContext

I'm trying to sort my NSFetchRequest result using a NSSortdescriptor using a key pointing to a NSDate value. My fetch results come out totally random for no clear reason.
The NSManagedObjectContext I'm using is updated with a save from a nested child context created on a subclass of NSOperation. I know all this is done successfully because I can get all the data needed from the parent (main) context. Fetching from it just wont sort on date!
Strange thing is; predicates for selecting the entities (called "Tweet") between two dates works just fine!
Here's some code to illustrate my problem:
NSSortDescriptor* timeDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"time"
ascending:NO
selector:#selector(compare:)];
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Tweet"];
[request setSortDescriptors:[NSArray arrayWithObjects:timeDescriptor, nil]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"((time >= %#) AND (time <= %#))",startDate,endDate];
[request setPredicate:predicate];
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[[NSApp delegate] managedObjectContext]];
[context performBlock:^{
NSError* error = nil;
NSArray* results = nil;
results = [context executeFetchRequest:request error:&error];
// Results here are not ordered correctly
// 2nd try sorting results using fetched array (works!)
results = [results sortedArrayUsingDescriptors:[NSArray arrayWithObjects:timeDescriptor, nil]];
// This works too but not needed anymore
/*results = [results sortedArrayUsingComparator:^(id obj1, id obj2) {
Tweet* tweet1 = (Tweet*)obj1;
Tweet* tweet2 = (Tweet*)obj2;
//return [tweet1.time compare:tweet2.time]; // ascending
return [tweet2.time compare:tweet1.time]; // descending
}];*/
if ([results count] > 0) {
for (uint i = 0; i < [results count]; i++) {
Tweet* tweet = [results objectAtIndex:i];
NSDate* date = Tweet.time;
NSLog(#"tweet date: %#", date);
}
}
}];
Can anybody tell me why the NSSortDescriptor isn't working for my fetches?
Thanks!
-- Update --
It seems the NSSortDescriptor works fine when I fetch from the main (parent) managedObjectContext on the main thread without using the performBlock method. This still doesn't help me do sorted fetches on a NSPrivateQueueConcurrencyType managedObjectContext. Creating the NSFetchRequest, NSSortDescriptor and NSPredicate inside the performBlock doesn't fix the problem either.
I hit the problem as well.
I've found out that unless the data is saved all the way back to Persistent Store, the sorting won't work if the data in the master context is dirty, i.e. modified.
For example, if the contexts are clean, without pending changes, the sorting works.
If I only change one attribute of an entity in the parent context, then the sorting in the private queue child context doesn't work. That's very unfortunate. I also do sorting with array method now but it's not that fast as sorting in the NSFetchRequest, especially since my data is already indexed by that key. It would've been much faster to sort it in the fetch request.
My guess is that since there are unsaved changes in the context and NSFetchRequest goes to the SQLite database itself, where the changes do not yet exist (context not saved), it can't sort on the database level at all.
But overall, it's very confusing and smells like a bug.
I had exactly the same issue. I have solved the problem by setting the includesPendingChanges property in the NSFetchRequest instance to NO.
When using the default compare: selector, you can simplify the descriptor:
NSSortDescriptor* timeDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"time"
ascending:NO];
But that's an aside. I think the key is the fact that you're updating from a nested child context. Validate that your objects have permanent object ids; they might not have received them yet, and thus that might be the issue with your fetch. If it is, then try calling objectPermanentIDsForObjects: prior to saving the nested child context.

Core Data: Keypath Error Not Found in Entity <NSSQLEntity Studies id=3>

Could any one tell me what's the wrong with this code? It raises the following error and cause application to crash:
reason: 'keypath Studies.patients.PatientName not found in entity <NSSQLEntity Studies id=3>'
Code:
- (void)viewDidLoad {
[super viewDidLoad];
test_coredataAppDelegate *appDelegate = (test_coredataAppDelegate *)[[UIApplication sharedApplication] delegate];
self.context = appDelegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Studies" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
/**/
NSLog(patientName);
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:
#"(Studies.patients.PatientName == %# )",patientName]];
NSError *error;
self.StudiessList = [_context executeFetchRequest:fetchRequest error:&error];
self.title = #"patients";
[fetchRequest release];
}
Firstly, since your fetch entity is Studies you don't include it in the predicate because the Studies objects are the ones receiving the predicate test in the first place. So your predicate should be at least just:
patients.PatientName == %#
However, by convention, patients would indicate a to-many relationship. If so, that means that the actual value of patients is a set of (presumably) Patient objects. As such you can't ask a set for an attribute value as above: Instead you have to ask for a new set of all object in the set that match the predicate. Use the ANY or All operator like so:
ALL patients.PatientName == %#
I would add that by convention all attribute and relationship names start with lower case letters so if PatientName is an attribute it should be patientName.
Either the Studies entity does not have a patient property, or whatever entity the patients relationship points to does not have a PatientName property (pay attention to upper/lowercase issues) or both.
Usually when I see this error it means that I added a new Version to my Model but forgot to update the Current Model Version for the project.
Posting this answer to help my future self because it's not the first time I have landed on this page!
In my case, I was referencing a superclass property of the entity but hadn't set the parent entity in the xcdatamodel file. I had to select the other NSManagedObject which I was subclassing from the inspector menu (default is None):

How to switch UITableView's NSFetchedResultsController (or its predicate) programmatically?

I have a UITableView that displays a subset of a large number of entities named "Documents". The subset is defined by another entity "Selection". Selections are named, ordered list of documents.
It Works fine, except when I want to change the displayed selection at run time. I get only a blank list.
Basically, I need to change the predicate that my NSFetchedResultsController holds so that the new predicate uses the another Selection. I couldn't make it work. My last attempt is to get rid of the NSFetchedResultsController altogether and reallocate it:
- (void) displaySelection:(Selection *)aSet
{
self.currentSelection = aSet;
self.fetchedResultsController = nil;
// methods here don't all use the property but directly the ivar, so we must trigger the getter
[self fetchedResultsController];
[self.tableView reloadData];
}
And of course, the NSFetchedResultsController getter does the right thing:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) { return fetchedResultsController; }
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setPredicate:predicate];
<snip>
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
<snip>
return fetchedResultsController;
}
This code works the first time, because the Initial Selection is set. When displaySelection: is called, though, the tableview becomes blank.
A very similar question was asked at NSFetchedResultsController fetch request - updating predicate and UITableView
And the answer was to get rid of the NSFetchedResultsController. I don't want to do that, because NSFetchedResultsController brings a lot of useful goodies here (eg caching, partial loading...). The question still stands: how to "switch" data in a UITableView backed by a NSFetchedResultsController, where "switch" means having a different predicate, or even (not in my case) a different entity.
Note for the sake of completeness, that since the many-to-many relationship from Selection to Document is ordered, it is handled through an in-between lightweight entity called DocInSelection, which has an "ordering" property and two many-to-one relationships to Document and Selection.
Thanks for any suggestion.
Since NSFetchedResultsController(FRC) is an object, you can store instances of it like any other object.
One useful technique is to initialize and store several FRC in a dictionary and then set the tableview controller's fetchedResultController attribute to the FRC you need at the moment. This is useful for situations such as having a segmented control to sort on different attributes or entities in the same table. This technique has the advantage of maintaining the individual FRC caches which can speed fetches up significantly.
Just make sure to send the tableview itself a beginUpdates before you swap controllers and then an endUpdates when you are done. This prevents the table from asking for data in the narrow window when the FRC are being swapped out. Then call reloadData.
After I posted my question, I tried a variant of the code the OP of the other question showed. It works for me. Here it is:
- (void) displaySelection:(Selection *)aSet
{
if (aSet != self.currentSelection) {
self.currentSelection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSPredicate *predicate = nil;
NSEntityDescription *entity = nil;
entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.tableView reloadData];
}
While this may work there's a note in the iOS Reference Library that troubles me:
Important: You must not modify the
fetch request. For example, you must
not change its predicate or the sort
orderings.
Source: NSFetchedResultsController Class Reference
This additional note doesn't exist in the iOS 3.2 Reference Library.
Just wanted to point this out.
An important note: if you "overwrite" a fetchController object make sure you clear its .delegate first - otherwise you'll get crashes when deleting rows, etc as the old fetchController and its delegate get events.