Using NSPredicate with Core Data NSFetchedResultsController - iphone

I have a fetchedResultsController that has returned all records for my entity "Account".
I would like to quickly search all Account records for the attribute "lastName" == value, and give me back the Account object, or at least the indexPath of the object in the fetchedResultsController. There should only be 1 object returned.
Other than iterate through every objectAtIndexPath, is there a better way to search the fetchController using NSPredicate?

mootymoots, just filter the fetched objects with another predicate...
NSPredicate *lastNameMatch = ...
NSArray *matchingFetchedObjects = [fetchedResultsController.fetchedObjects filteredArrayUsingPredicate:lastNameMatch];
That leaves your fetchedResultsController results un-altered, but gives you an array with a match for the last name predicate.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// set up fetch request
...
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(lastName like '%#')", self.lastName]];
[fetchRequest setPredicate:requestPredicate];
...
// perform fetch
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// handle error...
}
...
Account *uniqueAccount = [[self.fetchedResultsController fetchedObjects] anyObject]; // assuming lastName attribute is unique

Related

iPhone how to use NSPredicate to filter Core Data by the parent entity?

My core data is defined as this:
user has many events;
event has a single user relationship;
Both user and event are core data entities. The user entity is passed in through a storyboard segue.
I'm trying to configure NSPredicate to populate the detail UITableView for that user with only events for that particular user.
So far I have tried
//does not work
NSPredicate* onlyThisUserPredicate = [NSPredicate predicateWithFormat:#"user == %#",self.appUser];
//does not work
NSPredicate* onlyThisUserPredicate = [NSPredicate predicateWithFormat:#"SELF.user == %#",self.appUser];
What is the proper syntax to compare events and only return those that have user object equal to the specified user object?
UPDATE:
I'm trying to be able to add events to the user with this kind of fetched results controller:
-(NSFetchedResultsController*)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:[Event managedObjectContext]];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
//I need to configure this user
NSPredicate* onlyThisUserPredicate = [NSPredicate predicateWithFormat:#"user = %#",self.appUser];
// The first sort key must match the section name key path key if present, otherwise the initial dataset would be messed up: rows in incorrect sections
NSString* firstSortKey = #"createDate";
NSSortDescriptor *firstSortDescriptor = [[NSSortDescriptor alloc] initWithKey:firstSortKey ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:firstSortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setPredicate:onlyThisUserPredicate];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[Event managedObjectContext] sectionNameKeyPath:nil cacheName:#"Events"];
self.fetchedResultsController = aFetchedResultsController;
aFetchedResultsController.delegate = self;
// [aFetchedResultsController release];
[sortDescriptors release];
[fetchRequest release];
NSError *error = nil;
if (![__fetchedResultsController performFetch:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
}
return __fetchedResultsController;
}
Thank you!
OK, there are a couple of things that I can think of that might cause this behavior.
First, have you validated the value of self.appUser within this function? Is it set to what you expect?
Second, have you made sure your headers are all up to date and included in this file? Sometimes I've experienced odd behavior when my headers aren't up to date with the coredata model.
So this predicate is for the User entity correct? If so, did you try this:
NSPredicate* onlyThisUserPredicate = [NSPredicate predicateWithFormat:#"SELF == %#",self.appUser];
Then you could access your events through:
[self.appUser events];
If you've already retrieved the 'user' from the Core Data store, then you should be able to access its events simply by following that relationship -- no need to do a separate fetch request:
NSSet *events = self.appUser.events;
On the other hand, if self.appUser isn't a managed object, then using the == operator in your predicate is probably the problem. So let me assume that self.appUser is just a string containing the name of the user, not the user object from the data store. Then you'd use the 'like' operator in your predicate:
NSPredicate* onlyThisUserPredicate = [NSPredicate predicateWithFormat:#"user like %#",self.appUser];
Also, be sure that you've specified the right entity in your fetch request. For what you've described, you should be doing the fetch with the entity description for your event entity.

NSFetchRequest for all children of a parent

How do I fetch all child entities of a parent?
I have a table populated by a parent entity in Core Data. When the user touches a cell I intend to show another table with all children of that parent.
How does the NSFetchRequest look like for this please?
Edit:
model is like this:
student>>dates [one to many, one student have many days]
So I want all dates for any given student (selected by touching in student table cell for that student), then populate dates table with dates for that student.
Thanks!
Assuming that the entity and the class names are Student and Date, and the reverse relationship for Date->Student is called student,
Student *aStudent = ...;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: [NSEntityDescription entityForName: #"Date" inManagedObjectContext: [aStudent managedObjectContext]]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"student == %#", aStudent]];
You don't need a separate fetch request for this. All of the objects from the to-many relationship (don't call them child entities, that is misleading and incorrect) are available by accessing the relationship from the student object - something like student.dates. This gives you an NSSet, you can sort it and turn it to an array if you need to.
Within your first table delegate, when you touch a specific cell, I'll inject the specific parent property to the second table controller. For example:
SecondController secondController = ... // alloc-init
secondController.studentToGrab = ...
where SecondController declaration has a studentToGrab property like the following:
#property (nonatomic, retain) Student* studentToGrab; // use strong with ARC, if non-ARC remember to release it
and in definition synthesize it.
Then in your second controller, within viewDidLoad method you could do:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourNameEntityForDate" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"student == %#", studentToGrab];
[fetchRequest setPredicate:predicate];
// you can also use a sortdescriptors to order dates...
NSError *error = nil;
NSArray *resultArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error != nil) {
NSLog(#"Error: %#", [error localizedDescription]);
abort();
}
// use resultArray to populate something...
A remark when you deal with table you could also use NSFetchedResultController class. It has advantages when used for displaying data in tables.
If you have custom classes, you could traverse the generated relationship (return [student dates]). That will get you an unordered NSSet on iOS4, or, you can do it with a fetch request (note I use ARC so no releases/autoreleases here):
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Date"
inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSMutableArray *predicates = [NSMutableArray arrayWithCapacity:3];
[predicates addObject:[NSPredicate predicateWithFormat:#"student == %#", aStudent]];
// You might add other predicates
[fetchRequest setPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:predicates]];
// and if you want sorted results (why not, get the database to do it for you)
// sort by date to the top
NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"dateAdded" ascending:NO]];
}
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *sorted = [moc executeFetchRequest:fetchRequest error:&error];
if (error) {
// Handle the error, do something useful
}
return sorted;

fetching objects from core data not in a set

I'm trying to fetch objects from core data that are not in a given set, but I haven't been able to get it to work.
For instance, suppose that we have a core data entity named User, which has a few attributes such as userName, familyName, givenName, and active. Given an array of strings representing a set of usernames, we can easily fetch all the users corresponding to that list of usernames:
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User"
inManagedObjectContext:moc];
[request setEntity:entity];
NSArray *userNames = [NSArray arrayWithObjects:#"user1", #"user2", #"user3", nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userName IN %#", userNames];
[request setPredicate:predicate];
NSArray *users = [moc executeFetchRequest:request error:nil];
However, I want to fetch the complement of that set, i.e., I want all the users in core data that don't have the usernames specified in the userNames array. Does anyone have an idea how to approach this issue? I thought it would be simple enough to add a "NOT" in the predicate (i.e., "userName NOT IN %#"), but Xcode throws an exception saying the predicate format could not be parsed. I also tried using the predicate builder available for fetch requests with no luck. The documentation wasn't particularly helpful either. Suggestions? Comments? Thanks for all your help :)
In order to find the objects that aren't in your array, all you have to do is something like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NOT (userName IN %#)", userNames];
That should return a request of all the objects without the ones you specified
I am not strong at core data/objective-c but the predicate should be like the following statement;
[predicateFormat appendFormat:#"not (some_field_name in {'A','B','B','C'})"];
An example:
NSMutableString * mutableStr = [[NSMutableString alloc] init];
//prepare filter statement
for (SomeEntity * e in self.someArray) {
[mutableStr appendFormat:#"'%#',", e.key];
}
//excluded objects exist
if (![mutableStr isEqual:#""])
{
//remove last comma from mutable string
mutableStr = [[mutableStr substringToIndex:mutableStr.length-1] copy];
[predicateFormat appendFormat:#"not (key in {%#})", mutableStr];
}
//...
//use this predicate in NSFetchRequest
//fetchRequest.predicate = [NSPredicate predicateWithFormat:predicateFormat];
//...
Here's another useful example, showing how to take a list of strings, and filter out any which DON'T start with the letters A-Z:
NSArray* listOfCompanies = [NSArray arrayWithObjects:#"123 Hello", #"-30'c in Norway", #"ABC Ltd", #"British Rail", #"Daily Mail" #"Zylophones Inc.", nil];
NSPredicate *bPredicate = [NSPredicate predicateWithFormat:#"NOT (SELF MATCHES[c] '^[A-Za-z].*')"];
NSArray *filteredList = [listOfCompanies filteredArrayUsingPredicate:bPredicate];
for (NSString* oneCompany in filteredList)
NSLog(#"%#", oneCompany);
I use this kind of NSPredicate when I'm populating a UITableView with an A-Z index, and want an "everything else" section for items which don't start with a letter.

Using NSPredicate to get an array of Core Data objects?

Say I have a Core Data entity called Person. How would I get an NSArray of Persons whose properties match certain values? For instance someone of a particular age, height, or weight... or someone with a whose height,weight and age are specific values...
Can I use an NSPredicate like so:
NSPredicate *pred =
[NSPredicate predicateWithFormat:
#"(age == 25) OR (height_in_cms == 185) OR (age == 30 AND height_in_cms == 170 AND weight_in_kgs == 80)";
// All properties are NSNumber
I'm not an expert on the syntax for predicateWithFormat:, but you have the basic gist. You can find details on the format in Apple's Predicate Programming Guide. If you're asking what to do with the predicate once you have it, here is a snippet that shows you the steps:
// Create a fetch request.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Set the entity for the fetch request.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[entity release];
// Set the predicate for the fetch request.
[fetchRequest setPredicate:predicate];
// Perform the fetch.
NSError *error;
NSArray *array = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
If you want the results to be sorted, you can pass an array of sort descriptors to the fetch request using setSortDescriptors: prior to executing the fetch.
You can follow the given statement if you have these value in a variable.
[fetchResults filterUsingPredicate:[NSPredicate predicateWithFormat:#"age == %i OR hieght== %i AND weight==%i",age,height,weight]];
And also your approach is correct in case for specific values but your statement having syntax error so maintain proper syntax

CoreData INSERT or REPLACE?

I'am currently migrating an iphone application using SQLite to CoreData.
I need to do an INSERT or REPLACE to add only the new content, is there a way to do this or do I have to fetch all the DB and look for existing objects and add the new ones ?
Thanks.
Remember, Core Data is an object hierarchy that happens to persist to a DB so you need to look at it like an object graph and not a database.
Therefore, yes you need to check to see if the object exists already using some defined unique ID and if it does not currently exist you need to create the new object vs. updating the existing object.
update
You don't need to fetch all of the objects, search the store using a NSFetchRequest and a NSPredicate to check for existence; if it exists update, if it doesn't create it.
Basically we order the inserted(fetched) data and the json data so at every index, they have equal values. We then retrieve the value at the respective index and update the managed object and in the case the counter is larger than the fetched results length, we insert a new managed object.
//Add JSON objects into entity
- (void) insertUpdate:(NSArray *)jsonArrayData {
//sort JSON Data
NSSortDescriptor *idDescriptor = [[NSSortDescriptor alloc] initWithKey:#"entityID" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortDescriptors = #[idDescriptor];
NSArray *sortedJSONArray = [jsonArrayData sortedArrayUsingDescriptors:sortDescriptors];
//get entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"entity" inManagedObjectContext:self.managedObjectContext];
// Get the ids from json in sorted order.
NSMutableArray *entityIDs = [[NSMutableArray alloc]init];
for (NSDictionary *jsonValues in jsonArrayData) {
id value = [jsonValues objectForKey:#"entityID"];
[entityIDs addObject:value];
}
//sort to make sure json and fetched data are both sorted in same manner
[entityIDs sortUsingSelector:#selector(compare:)];
// create the fetch request to get all Entities matching the IDs
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
NSString *filter = #"(%K IN %#)";
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: filter, #"entityID", entityIDs]];
// Make sure the results are sorted as well.
[fetchRequest setSortDescriptors:
#[ [[NSSortDescriptor alloc] initWithKey: idName ascending:YES] ]];
// Execute the fetch.
NSError *fetchError;
NSArray *entityMatchingNames = [self.elRehabCoreData.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
int i = 0;
//loop over JSONData
for (NSDictionary *keyedValues in sortedJSONArray) {
id value = [keyedValues objectForKey:#"entityID";
//Create NSManagedObject
NSManagedObject *managedObject = nil;
int updateInsert = 0;
if(entityMatchingNames.count > i ){
//update
managedObject = [entityMatchingNames objectAtIndex:i];
if ([[managedObject valueForKey:#"entityID"] isEqual:[keyedValues valueForKey:#"entityID"]]){
updateInsert = 1;
}
}else{
//insert
managedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
updateInsert = 1;
}
i++;
//set value if updateInsert is true
// The updateInsert flag is an extra security to make sure we only update when the value in JSON data is equal that of fetched data
if (updateInsert) {
[managedObject setValue:value forKey:attribute];
}
}
//save core data stack
[self saveContext];
}