setPropertiesToFetch doesn't seem to have any effect - iphone

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

Related

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.

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

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.

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];