NSFetchRequest for all children of a parent - iphone

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;

Related

Core Data to-many relationship get data

In my code i want to create tableView with List sections. I use scheme like this one:
I use NSFetchResultController which i define in this way:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Item" inManagedObjectContext:coreDataController.masterManagedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"addedAt" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"itemIsChecked = 1"];
[fetchRequest setPredicate:predicate];
[fetchRequest setResultType:NSDictionaryResultType];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:coreDataController.masterManagedObjectContext sectionNameKeyPath:#"toList.listName"
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Now in cellForRowAtIndexPath: i want to get data form my fetchResultController, so i do this in way:
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
and then if i want to access one of the item's field (for example itemText), it crash:
NSLog(#"item itemtext = %#", item.itemText);
with error:
-[NSKnownKeysDictionary1 itemText]: unrecognized selector sent to instance 0x1215fd90
What i do wrong in my code?
You have set
[fetchRequest setResultType:NSDictionaryResultType];
and therefore the fetched results controller returns NSDictionary objects, not Item objects. So your element
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
is a NSDictionary, not an Item. Since dictionaries do not have a itemText method, item.itemText crashes. You could retrieve the value from the dictionary with
NSDictionary *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"item itemtext = %#", [item objectForKey:#"itemText"]);
But if you don't have a specific reason to set the result type to NSDictionaryResultType, you should just delete that line. Change tracking of the fetched results controller (i.e. automatic table view updates) do not work with resultType == NSDictionaryResultType.
Note also that if you have set a sectionNameKeyPath, then you must add a sort descriptor with the same key path "toList.listName" and use it as first sort descriptor for the fetch request.
unrecognized selector sent to instance generally occurs due to bad memory management. Check if you are trying to point an object which was released earlier. Also check for IBOutlet connection in xib for lable itemText.

Help me to get the result based on condition

I have created a Users class based on NSManagedObject with following attributes (id,name,age etc).
I am using the core data model but i am not sure how to do the follwing...
Now i would like to know How can i get the user detail based on user id.
example: select * from users where id = 1
please help me out.
You should use NSPredicate class for executing SQL commands. The code:
NSManagedObjectContext *context = self.managedObjectContext; // specify your MOC object
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"users" inManagedObjectContext:context]; // specify your entity (table)
NSPredicate *predicate = [NSPredicate predicatewithFormat:#"id == %d",yourID]; // specify your condition (predicate)
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:fetchRequest error:&error]; // execute
[entity release];
[predicate release];
[fetchRequest release];
if (array == nil) {
// Error: no objects returned
} else {
// Success: do whatever you want
}
Step 1: Alloc/init NSFetchRequest
You need to alloc/init a NSFetchRequest object if you want to execute queries.
Step 2: Select entity
If you want to specify select * from users ..., you should use NSEntityDescription:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"users" inManagedObjectContext:context];
At the end you need to 'attach' your entity description to your NSFetchRequest object via:
[fetchRequest setEntity:entity];
Step 3: Condition
If you want to have a condition (e.g. ... where id = 1), you have to implement NSPredicate.
NSPredicate *predicate = [NSPredicate predicatewithFormat:#"id == %d",yourID];
yourID must be a number (e.g. 1, 2, 7 or 46).
And, again:
[fetchRequest setPredicate:predicate];
Step 4: Let's execute it!
NSArray *array = [context executeFetchRequest:fetchRequest error:&error];
All the records that meet the conditions will be returned as array of NSManagedObjects.
Step 5: Release objects
[entity release];
[predicate release];
[fetchRequest release];
Step 6: Do something
If there are no objects that meet the conditions, array object will be nil. You can check it and deal with the error via:
if (array == nil)
Check out Core Data Programming Guide for more info. :)

Quick Question About NSFetchRequest and Relationship

In my Core Data model, I have an entity Session and Exercise.
Session has a to many relationship to Exercise (there is a one-one inverse relationship as well).
In my fetch, I am trying to find all Session object that are related to the current Exercise.
I am using the following code which isn't working.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"exercise = %#", exercise.name]];
NSEntityDescription *sessionEntity = [NSEntityDescription entityForName:#"Session" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:sessionEntity];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"Fetch error: %#", error);
self.sessionArray = results;
Here is my data model:
First, from the screenshot it seems that your relationship attribute of the Session entity is called exercises not exercise.
Also, it seems to me that it would work if you searched not the Session entity but the Exercise entity and then iterate through the resulting array to extract the sessions.
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:"name == %#", exerciseName]];
NSEntityDescription *exerciseEntity = [NSEntityDescription entityForName:#"Exercise" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:exerciseEntity];
...
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
...
NSMutableArray *mutableSessionArray = [NSMutableArray array];
for (Exercise *ex in results) {
Session *session = [ex exercises];
if (session) [mutableSessionArray addObject:session];
}
self.sessionArray = [NSArray arrayWithArray:mutableSessionArray];
The relationship of the Exercise entity called exercises could be called session for clarity. I would recommend renaming it.
BTW, you can also write = instead of ==, they are equivalent, as far as I know. In the Predicate Programming Guide section on Predicate Format String Syntax it says:
=, ==
The left-hand expression is equal to the right-hand expression.
In your predicate you use =, == is the comparison operator. I think that's where you're going wrong.
Source: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSPredicate_Class/Reference/NSPredicate.html

iOS CoreData update entity object

I'm implementing a car manager for an iPhone app and I have trouble saving a new car. So I have a "Car" entity (and per-loaded DB), containing multiples attributes. I have 2 booleans "saved" and "selected" to track which car the user added to his list (if added, saved = 1) and which car is currently selected. So when I create a new car, I deselect the old one (selected=0), and want to modify the new car to set its attributes saved=1 and selected=1.
Here is my functions:
- (IBAction)save
{
// Disable the previous car selection.
[self deselectCurrentSelectedCar];
// Add new car as saved and selected.
[self saveAndSelectNewCar];
// Call the delegate to dismiss the modal view.
[_delegate dismissAndSave];
}
- (void)deselectCurrentSelectedCar
{
// Fetched saved car.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selected = 1"];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: car not found.");
}
else {
// Get car and assign new selected value.
Car *carToSave = (Car *)[mutableFetchResults objectAtIndex:0];
[carToSave setSelected:[NSNumber numberWithInt:0]];
// Save the car.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: error saving car.");
}
else {
NSLog(#"[AddCarViewController] deselect car: car saved.");
}
}
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
- (void)saveAndSelectNewCar
{
// Get the car, and pass to the delegate the new settings.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(year=%#) AND (make=%#) AND (model=%#) AND (d=%#) AND (n=%#)", _car.year, _car.make, _car.model, _car.d, _car.n];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle error.
NSLog(#"[AddCarViewController] save and select: can't save the new car");
}
else {
// Get the car selected.
Car *selectedCar = (Car *)[mutableFetchResults objectAtIndex:0];
[selectedCar setValue:[NSNumber numberWithInt:1] forKey:#"selected"];
[selectedCar setValue:[NSNumber numberWithInt:1] forKey:#"saved"];
// Save the car.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"[AddCarViewController] save and select: error saving car.");
}
else {
NSLog(#"[AddCarViewController] save and select: car saved.");
}
// Add car to delegate.
[EcoAppAppDelegate setUserCar:selectedCar];
}
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
And I have this log all the time: "error saving car." on both functions. So there is definitely something wrong.
Also, it's pretty anoying to fetch the car I want to modify it, instead of doing right away an update. If there another please tell me!
Thanks.
In your header file, you should set up a mutable array for your cars.
NSMutableArray *carArray;
and
#property (nonatomic, retain) NSMutableArray *carArray;
Then make sure to synthesize it in your implementation file. Then when you fetch from your managed object context, you can set your array with the contents returned
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: car not found.");
} else {
[self setCarArray:mutableFetchResults];
}
Doing this will hold onto all the objects in your managed object context, so when you want to modify a Car, you can find it there instead of fetching again. If you need to sort it, you can apply a sort descriptor initialized with one of your attributes as a key. For example:
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:#"selected" ascending:YES];
[carArray sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
This will sort the Cars based on whether they are selected or not.
As for why you're getting an error, that could be one of many reasons. Typically the MOC will fail to save if one of your attributes is set to nil, so that might be the cause. You can get some detail from it if you set something like the following up
if (![self.managedObjectContext save:&error]) {
NSLog(#"failed with error %#", error);
}
This will return the actual error you ran into. It would also be a good idea to set up a log to make sure you have Car specified. Something like NSLog(#"selected car %#", carToSave); just to be safe

Core Data: Returning properties depending on a value

can anyone guide me how to create a fetch request that will query an entity and returns any properties that qualify my criteria.
Here's what I have. I have an entity that has 35 properties, all are in types of float.
What I need was to see all properties of the entity which values was <= zero.
I know how to return the values of the properties but not how to return the name of the property.
Thanks,
CoreData doesn't return properties. It returns entities, which then have properties. In any case, you'd have to do something like this:
(The following is pseudo-code done from memory. Treat it accordingly.)
NSString *query = #"(property1 <= 0) && (property2 <= 0)";
NSPredicate *predicate = [NSPredicate predicateWithFormat:query];
NSEntityDescription *entity = [NSEntityDescription entityDescriptionForName:#"Entity" inManagedObjectContext:context];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
NSError *error = nil;
NSArray *entities = [context executeFetchRequest:fetch error:&error];
// Let's just assume we got one
NSManagedObject *obj = [entities objectAtIndex:0];
float value = [obj valueForKey:#"property1"];
Or something like that.