Sections from already queried Entity - iphone

So i'm displaying some Entities in a UITableView. With clicking on a Cell i want to show other Entities that are already queried in a "to-many" Relationship.
For example i'm displaying all Classes of a School. Now i want to display all Students of a Class. This Students are already available as an NSSet under Class.students
Now i want to display the Students in different Sections following by their first Letter.
If i wanted to get them directly from CoreData, i would do something like
// init fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Student" inManagedObjectContext:self.managedObjectContext];
// Search only specific students
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"class == %#", theClassThoseStudentsBelongTo];
[fetchRequest setPredicate:predicate];
// Generate it
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"firstLetter"
cacheName:#"StudentTable"];
with this method i would get them nicely arranged into sections.
But i already have all students for a specific Class. is there a way to init a NSFetchedResultsController with a initialized NSSet or to do something equal?
Sure, i could arrange my NSSet manually but isn't there such a nice way like it is for a new query?
thanks in advance.
Please leave a comment if something is unclear.

I guess you only have 2 options: using NSFetchedResultsController or sorting the objects on your own.
NSFetchedResultsController & NSPredicate:
Pros: easy object deletition; notifications of model changes, e.g. during syncing
Cons: unnecessary refetch
NSSet & NSSortDescriptor
Pros: no refetch
Cons: complicated deletition; no notifications of model changes, e.g during syncing: you could be displaying a student that has already been deleted

you could use a NSPredicate which uses the reverse relationship (from your students back to the class)
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"class == %#", theClassThoseStudentsBelongTo];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController = ...

The relationship of objects fetched by NSFetchedResultsController start out as faults and are only fetched when needed. This means that if we don't use the "Student" entities for the first tableView it will be lazily loaded only when we need it.
However, since you need to know the count of students the situation is a little more complicated since calling [class.students count] will fire the fault. (Calling the KVO #count will fire the fault also).
So you have two options:
managed an attribute called studentsCount in class that reflects the number of entities in students. Calling this attribute will not fire a fault on the relationship.
use countForFetchRequest:
NSFetchRequest *req = [[NSFetchRequest alloc] init];
[req setEntity:[NSEntityDescription entityForName:#"Student" inManagedObjectContext:context]];
[req setPredicate:[NSPredicate predicateWithFormat:#"class = %#", myClass]];
The second option performs a fetch, but a very efficient one, so maybe it's efficient enough - i didn't do performance tests so i can't really say.
By the way, if you're not sure whether or not the relationship fired a fault you can use the method hasFaultForRelationshipNamed.

Related

iPhone, Core Data, Predicate with "self" problem

I need to reload a Person NSManagedObject before I pass it onto the next View.
This is because the fetchedResultsController I'm using is only returning a subset of attributes and I need the full set in the next view.
Thus far I'm trying something like:
- (void)tableView:(UITableView *)tableViewPassed didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Person *partialPerson = (Person *)[self.fetchedResultsController objectAtIndexPath:indexPath];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:[partialPerson.managedObjectContext]];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
...
Now I can't seem to get the predicate to do this working correctly so far I've tried:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF == %#", partialPerson];
and
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF == %#", partialPerson.objectID];
But neither of these seem to work. What am I doing wrong here? Is this a good approach?
Thanks in advance for any suggestions,
Matt
You just need to access the attributes that are not faulted and they will get faulted automatically. You do not need to refetch the object at all.
Update
Storing images or any binary data in Core Data has some basic rules to follow:
< 100kb store it in the same entity
< 1 mb store it in a separate entity on the other end of a relationship
> 1 mb store it on disk and reference it in the same entity
Sounds like you are storing too much binary data in the primary table. To correct this follow the rule above and it will solve your problem.
But that does not negate my original answer in that you can instruct your fetch to pull in attribute1, attribute3 and attribute 5 and when you need to access attribute3, you just access it and Core Data will "do the right thing" and load it when you try and access it. There is never a reason to "reload" the object in this situation.
Why you need to refetch partialPerson?
Simply pass it to the next view and you are done!!! You can do all you want with partialPerson variable on the next view. I don't see any reason why you need to refetch it.
myViewController.myPartialPerson = partialPerson;

iPhone Core Data - Simple Query

I am trying to create a Core Data iPhone application. One of the entities I'm tracking is cars, and one attribute of each car is "manufacturer".
In the "edit car" section of my application, I have a UIPickerView that needs to be loaded with each of the unique manufacturers that have previously been entered into the system. What I'm trying to do is create an NSFetchRequest to get an array of unique "manufacturer" attributes and use that to populate the UIPickerView.
The problem I'm running into is that whether there are zero records or 100 in the data store, there is always one record in the executed fetch request at element zero with a value #"".
Am I doing this wrong or is there an easier way to do this? I wish I could just run a quick sql query!!!
My code is below:
// Populate the manufacturerNameList array
NSManagedObjectContext *moc = [self.selectedLease managedObjectContext];
NSEntityDescription *ed = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:moc];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:ed];
// Get only manufacturer and only uniques
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:#"manufacturer",nil]];
[fetchRequest setReturnsDistinctResults:YES];
// Sort by manufacturer in ascending order
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"manufacturer" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
self.manufacturerNameList = [moc executeFetchRequest:fetchRequest error:&error];
if (error) {
// Handle the error
}
NSLog(#"The count of self.propertyNameList is %i",[self.propertyNameList count]);
Thanks!
manufacturerNameList is going to be an array of Car entities, not manufacturer names. Also, you need to pass an NSArray of NSPropertyDescription objects to setPropertiesToFetch not just attribute names.
Here is how you set the property:
NSDictionary *entityProperties = [ed propertiesByName];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:[entityProperties objectForKey:#"manufacturer"], nil]];
The results from executeFetchRequest: will be an NSArray of Car entities, so you'll then have to extract the manufacturer attribute values in a loop or something.
You may want to consider creating a Manufacturer entity that your Car entity references, that will allow you to query more in the way you are attempting to right now.
Another approach would be to create an entity for manufacturers and have a relationship between Car and Manufacturer such that a Car has one Manufacturer and a Manufacturer has many Cars:
Car <<--> Manufacturer
The Manufacturer entity could have a string attribute its "name".
Then, you could get the full list of manufacturer names by fetching all the Manufacturer objects and looking at the "name" property.
The simplest explanation is that you have a car entity that has an empty manufacturer value. When the predicate sorts the fetch, the blank string will be ranked first.
I would log the entire self.propertyNameList and see what you're actually getting back.

setPropertiesToFetch doesn't seem to have any effect

I'm trying to use setPropertiesToFetch in my fetch request to limit the data that is retrieved from my store, but it seems to have no effect. When I use it and display the object returned into the console, I can see all my properties are there. The same data is displayed whether I set the properties or not. All the relationships display as faults, but all the data for the attributes is there.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Entity" inManagedObjectContext:context];
NSDictionary *entityProperties = [entity propertiesByName];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
[fetchRequest setIncludesPendingChanges:NO];
[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:[entityProperties objectForKey:#"myAttrib"], nil]];
The fetch seems to return the same data per object with or without that last line. Any ideas?
The Special Considerations section of the documentation for setPropertiesToFetch: says
This value is only used if resultType is set to NSDictionaryResultType.
Your code snippet isn't setting the resultType. Perhaps you meant to use setRelationshipKeyPathsForPrefetching:?
The impression I had (from what the Apple engineers have said) was that the data would be faulted in for the non-fetched properties as soon as you used the accessors for that property. It may be that in generating the description of the NSManagedObject, these accessors are being used for each property, causing the data to be faulted in right before the string describing the objects is generated.
You could try using the Core Data Faults and / or Core Data Cache Misses instruments (in the simulator) to see when the faults actually occur. If they happen right before you print out the managed objects, that would seem to support my guess above.
try setReturnsDistinctResults:YES
from the apple docs:
setReturnsDistinctResults:
Sets whether the request should return only distinct values for the fields specified by propertiesToFetch.
(void)setReturnsDistinctResults:(BOOL)values
Parameters
values
If YES, the request returns only distinct values for the fields specified by propertiesToFetch.
The correct way of using setPropertiesToFetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setResultType:NSDictionaryResultType]; // Remember to setResultType
[fetchRequest setPropertiesToFetch:
[NSArray arrayWithObjects:#"name", #"age", nil]];
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest
error:nil];
NSArray *nameArray = [results valueForKey:#"name"];
NSArray *ageArray = [results valueForKey:#"age"];
results is not an array of Person objects, but an array of Dictionary. You can access the Dictionaries inside like this
NSLog(#"%#", [results[0] valueForKey:#"name"]);
NSLog(#"%#", [results[0] valueForKey:#"age"]);
If you only want to work with Model object (which CoreData fetches all properties/attribues of the entity), you can design your Model with Person and PersonDetail (which holds detail information about a person). This way
You can perform fetchRequest and get an array of Person objects
When accessing aPerson.detail (detail is a one-to-one relation with PersonDetail), CoreData will perform faulting for you

Core Data : How to check for the presence of Many to Many relationship

I have a "Song" Entity and a "Tag" entity and they have a many to many relationship between them. A Song can have multiple Tags and a Tag can be applied to multiple Songs.
I want to check if a Song has a particular Tag associated with it. If the Song has the Tag associted with it, I want to show a checkmark in the table view.
For a similar logic, in Apple "TaggedLocations" sample code, the following check is made to check for the presence of the relationship.
if ([event.tags containsObject:tag]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This may be inefficient if there are a lot of Tags in the database as this will fetch all of them in the memory. Please correct me if I am wrong here.
Is there a more efficient way to check if the Song is associated with a particular Tag instead of checking in Song.Tags?
It's actually pretty easy to do, if completely undocumented. You want to create a fetch request with a predicate that has a set operation. If we imagine that your Tag model has a property called tagValue, the predicate you care about is "ANY tags.tagValue == 'footag'"
NSString *tagSearch = #"footag";
// However you get your NSManagedObjectContext. If you use template code, it's from
// the UIApplicationDelegate
NSManagedObjectContext *context = [delegate managedObjectContext];
// Is there no shortcut for this? Maybe not, seems to be per context...
NSEntityDescription *songEntity = [NSEntityDescription entityForName:#"Song" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:songEntity];
// The request looks for this a group with the supplied name
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.tagValue == %#", tagSearch];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
[request release];
You are correct, using that code will retrieve the entire set and the object comparison may be quite complex, depending on how many properties and relationship are part of the object's entity.
Anyway, you can not avoid a set comparison for inclusion. Probably, the best you can do is to avoid fetching all of the properties/relationships by asking Core Data to retrieve NSManagedObjectID Objects only.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Tag" inManagedObjectContext:[self managedObjectContext]]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSManagedObjectID objects are guaranteed to be unique, therefore you can safely use them to check for set inclusion. This should be much more efficient for a performance perspective.

iPhone CoreData Queries

I'm new at using CoreData and I'm trying to understand how to perform a query on a table. I can use a fetch request to pull all of the records from a table, but I'm looking for a subset. Is there an easy way to do this?
Thanks,
Howie
Have you looked into Predicates?
Also, buy Marcus Zarra's book on Core Data.
You can add a NSPredicate to the NSFetchRequest to filter the records that are returned. You can also control what is populated in the returned objects (only populate properties, include relationships, populate nothing, just return a count, etc.) but as Peter pointed out, Core Data is an object hierarchy and model API that just happens to store to a database. It is far easier to work with when you look at it from that POV.
You have to do something like:
// Init your fetchRequest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"entityName" inManagedObjectContext:yourManagedObjectContext];
// create the relation between request and the created entity
[fetchRequest setEntity:entityDescription];
// Set your predicate for this request
// For more info take a look at NSPredicate Class Reference
// http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPredicate_Class/Reference/NSPredicate.html
[fetchRequest setPredicate:somePredicate];
// Pushing the results into a mutable array
NSMutableArray *mutableFetchResults = [[yourManagedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
[fetchRequest release];