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];
Related
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.
I have a question in core data:
there are 2 Entities in the project, Books and Pages;
there are 3 Objects books user created in Entity Books;
there are several Objects pages user created in Entity Pages;
relationship inbetween is one page belongs to one book, one book has many pages.
and my question: there are 3 book with the same object name "book",and each has unique attribute .bookName : #"metal" ;#"plastic" ;#"glass". how to set page to the book with .bookName = #"glass" ?
//In BookViewController
Books *book = (Books *)[NSEntityDescription insertNewObjectForEntityForName:#"Books" inManagedObjectContext:managedObjectContext];
//textView.text is user input text
book.bookName = textView.text;
//In PageViewController
Pages *page = (Pages *)[NSEntityDescription insertNewObjectForEntityForName:#"Pages" inManagedObjectContext:managedObjectContext];
page.itsbook = WHAT?
Thank you for reading, I stuck here like: a day, really appreciate your help, love you!
If you need to find a specific book then you need to use a NSFetchRequest to ask Core data for it.
Quite some code is needed, so you probably add a convinience method to your Bookclass that looks something like this:
+(Book*)bookWithName:(NSString*)name
{
// 0. I assume you have something like this to get the context…
NSManagedObjectContext* context = [NSManagedObjectContext threadLocalContext];
// 1. Create an empty request
NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
// 2. Set the entity description for the object to fetch
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Book"
inManagedObjectContext:context];
[request setEntity:entity];
// 3. Set a predicate asking for objects with a mathing bookName property
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"%K LIKE[cd] %#",
#"bookName",
name];
[request setPredicate:predicate];
// 4. Execute the request on the managed object context.
NSArray* objects = [context executeFetchRequest:request error:NULL];
// 5. Result is an array, maybe handle empty array and many objects?
return [objects lastObject];
}
I'm not sure what you mean. If you want to insert a page as a subset of the book you want this line of code:
[book addPageToBookObject:page]; // the method will be the relationship name
If you want to get the book from the page object you'll need an inverse relationship from the pages to the book.
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;
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
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.