iPhone Core Data - Simple Query - iphone

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.

Related

Core Data: Save unique object ID

I see that there is a method of getting the unique id of a managed object:
NSManagedObjectID *moID = [managedObject objectID];
But, as I have read, that changes when it is saved.
What is a better way of creating my own unique ID and saving it in each object, making sure that it IS unique and does't change?
You cannot save the NSManagedObjectID in a CoreData entity, and there's no properties in it that can be intended as integer or string ID.
Therefore building your own unique identifier algorithm is an acceptable solution, if it's impossible for you to keep track when the entity is saved, I did this for some applications.
For example I had a similiar problem time ago, when I needed to fill a UITableView cells with a reference to the entity, and retrieve the entity after clicking/touching the cell.
I found a suggestion by using the uri representation, but however I still needed to save the context before, but I manage to use the NSFetchedResultsController and found a more solid solution rather than an application built id.
[[myManagedObject objectID] URIRepresentation];
Then you can later retrieve the managed object id itself:
NSManagedObjectID* moid = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
And with the moid I could retrieve your managed object.
I have a created date property in my object, so I just ended up using that date including seconds, which, seems to me like it will be unique and work how I want.
You can create an id field for your object, and populate it during init using GUID. for how to create a GUID and optionally export it to string see UUID (GUID) Support in Cocoa
If it helps anyone else searching for this, this is how I do it:
Create an ID property on the object
Get the last used ID when creating an object with this code:
Playlist *latest;
// Define entity to use and set up fetch request
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Playlist" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define sorting
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"listID" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
// Fetch records and handle errors
NSError *error;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults) {
NSLog(#"ERROR IN FETCHING");
}
if ([fetchResults count] == 0) {
latestID = 0;
} else {
latest = [fetchResults objectAtIndex:0];
latestID = latest.listID;
}
Increment this by one on the new object.

Sections from already queried Entity

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.

Sort on to-many relationship using a NSFetchedResultsController

I'm trying to use the NSFetchedResultsController in my app, but have a problem to sort my data. I get the following error when trying to sort the result using a relationship that is two levels down from the entity:
* Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'to-many key not allowed here'
My data model is set up this way:
Item <<---> Category <--->> SortOrder
<<---> Store
In other words: Each item belongs to one category. Categories can have different sort orders for each store that includes a certain category.
So, I'm creating a fetch request to find all items for a certain store and would like to present the result using category names as sections, and sorted on the sort order.
When I perform the the fetch (last line below), I get the above error.
NSManagedObjectContext* context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(status != %d) AND (ANY category.sort.include == YES) AND (ANY category.sort.store == %#)", ItemStatusDefault, store];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"category.sort.order" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
self.resultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"category.name"
cacheName:nil];
[fetchRequest release];
NSError *error;
BOOL success = [self.resultsController performFetch:&error];
If I change the sorting to, say, category names, it works.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES];
How can I get the NSSortDescriptor to sort on the sort order?
UPDATE:
So it seems this is not possible. I got a suggestion to create a transient property and sort on that, but Apple documentation clearly states
You cannot fetch using a predicate
based on transient properties
My conclusion here is that I cannot use NSFetchedResultsController out of the box. I need to either access the array of objects the NSFetchResultsController gives me and sort in memory, or setup my own fetch requests and skip NSFetchedResultsController.
iOS 5 provide now ordered relationships
https://developer.apple.com/LIBRARY/ios/releasenotes/DataManagement/RN-CoreData/index.html
UPDATE:
Link updated
Reference : "Core Data Release Notes for OS X v10.7 and iOS 5.0"

Core data, sorting one-to-many child objects

So, lets say I have a store of parents children and the parent has a one to many relationship to children (parent.children) and they all have first names. Now, on the initial fetch for parents I can specify a sort descriptor to get them back in order of first name but how can I request the children in order? If I do a [parent.children allObjects] it just gives them back in a jumble and I'd have to sort after the fact, every time.
Thanks,
Sam
If you just want to use an NSArray, and not an NSFetchedResultsController, there's another way:
NSSortDescriptor *alphaSort = [NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES];
NSArray *children = [[parent.children allObjects] sortedArrayUsingDescriptors:[NSArray arrayWithObject:alphaSort]];
Sam,
If I read your question correctly, you want to set up a fetch that returns a sorted list of the children of a specific parent. To do this, I would set up a fetch for "children" entities and then use a predicate to limit the results:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"children" inManagedObjectContext:moc]];
[request setSortDescriptors:[NSArray initWithObject:[[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES]];
[request setPredicate:[NSPredicate predicateWithFormat:#"(parent == %#)", parent]];
Obviously, your entity and attribute names may be different. In the last line, the parent variable should be a reference to the NSManagedObject instance of the parent whose children you want.

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