NSFetchedResultsController with a one-to-many relationship - iphone

I'm looking to display data from a Core Data model in a UITableViewController. Model has two entities, with a one-to-many relationship. I want the items from the many entity to be the rows, broken down into sections by the one. In the event no rows exist, I still want the section header to display.
I have a working NSFetchedResultsController table working for the many table only, but I need to expand it to the one-to-many relationship. I'm having no luck making that work, and haven't found any examples on how to do this.
The table will display all items, not a subset.
Any suggestions?

Since you didn't provide any details about the model objects your using, my example will use the following model.
The first thing I would suggest doing is look at the documentation for NSFetchedResultsController, specifically initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:. it tells you everything you need to know about fetching you data into sections.
The important part is that the first sort descriptor must return a similar order as the sectionNameKeyPath.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Book"];
NSSortDescriptor *librarySort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *bookSort = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
[fetchRequest setSortDescriptors:#[librarySort, bookSort]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"library.name"
cacheName:nil];

Related

Default fetchrequest result order changed every time

i am simply do a executedFetchRequest for an ENtity say #"tanId" it contiains 5 records 1,2,3,4,5
--> problem is first time it shows like 2,3,4,5,1
--> if again running it shows like 4,2,1,3,5
super dooper good
any one tell how to rectify this problem.
You can use an NSSortDescriptor to sort your NSFetchRequest. Otherwise, order is not guaranteed. There are some examples in the Fetching Managed Objects section of the Core Data Programming Guide.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"firstName" ascending:YES];
[request setSortDescriptors:#[sortDescriptor]];

iPhone iOS Core Data should I subclass an abstract entity for my entities?

Currently I have the following data model in my app:
Record is an abstract entity with timestamp, type, name. I have 3 types of entities that subclass Record. My NSFetchedResultsControllers pull Record objects and I sort them by class in my table views:
Event1 has record as abstract parent, there will be 10000 such events
Event2 has record as abstract parent, there will be 100 such events
Event3 has record as abstract parent, there will be 100 such events
Event 1 is generated by the system, while events 2 and 3 are created by the user.
The intention of this system is so it is easier to display data when all data is required.
However, I'm running into an issue where I want to display only partial data - user-created events and allow the user to edit them.
I'm wandering if my current data model is an effective way of filtering and displaying only user events (core data would have to separate only a handful events from the system-generated events). Should I make the system-generated events a separate entity? Should I even worry about things like this, or is core data optimized enough so stuff like this does not matter?
Below is the NSFetchedResultsController that made me ask this question:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//system-generated events do not have type defined, while user events do have a type
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"SELF.type > %0"];
[fetchRequest setPredicate:predicate];
//^What are the performnance implications of the above predicate?
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
Alex,
If all you want are the user events then just use a fetch request on that entity, not the superclass.
Andrew

NSFetchedResultsController and Relationship

This time I get a strange behavior with NSFetchedResultsController. I create a fetchRequest like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entdesc = [NSEntityDescription entityForName:#"Exam" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entdesc];
NSPredicate *predi = [NSPredicate predicateWithFormat:#"student == %#", self.student];
[fetchRequest setPredicate:predi];
If I execute it with executeFetchRequest:error: of NSManagedObjectContext, I get the expected Result. All Exams according to the student. (Between Student and exam is a one-to-many relationship)
But If I use the same fetchRequest in a NSFetchedResultsController, I get something different. Until now I didn't get out, what I exactly get. In my eyes the result is random.
Can you help me? I want to manage the exams of a given student with a NSFetchedResultsController.
Sandro Meier
If you have a Student object already in hand, you don't have to fetch the Exam objects you just ask the Student object for the contents of its exams relationship. There is no need to fetch because you already have a reference to all the Exam objects you want.
As to why the fetch works outside the fetch results controller, I can't say with certainty. The controller does nothing but take the results of a fetch and package them for display in a tableview. If the data does not display properly in the tableview, then the problem is most likely in the tableview delegate/datasource methods where you connect the contents of the fetched results controller to tableview.

Is Core Data more efficient than custom indexing for "as-you-type" searching?

Actually, I'm working on a Core Data based iPhone application. I have two entities which contain more than 200000 rows each and I'm experiencing some performance issues during retrieval of data. For each fetch request, I must wait between 1 and 2 seconds before getting results.
I'm thinking of implementing a custom search engine to index my data but the problem is that the whole database is editable. The contents can be changed at anytime so indexing a dynamic content database is stupid.
I'm wondering if Core Data is efficient enough to provide instant search. In the Apple documentation, a thousand rows entity is considered as small. Is that right?
Does anyone have a solution to improve the speed of Core Data...? Or should I implement my own search engine?
The goal is to provide an instant search, a search as you type mecanism.
[UPDATE]
Below is a snippet of one of my fetch request...
NSString *predicateString = [NSString stringWithFormat:#"^(.*\\b)?%#(\\b.*)?$", searchString];
NSString *predicate = [NSString stringWithString:#"text MATCHES[cd] %#"];
NSArray *arguments = [NSArray arrayWithObjects:predicateString, nil];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:[[HYDataManager instance] managedObjectContext]]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:predicate argumentArray:arguments]];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:#"length" ascending:YES], [[NSSortDescriptor alloc] initWithKey:#"subEntity.attr1" ascending:YES], [[NSSortDescriptor alloc] initWithKey:#"subEntity.attr2" ascending:YES], nil]];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"subEntity"]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[HYDataManager instance] managedObjectContext] sectionNameKeyPath:nil acheName:nil];
You can try several things.
1) try using setFetchBatchSize: to reduce the working set of data in your app. In combination with an NSFetchedResultsController this will transparently faults batches on demand, showing the objects on your table as needed.
2) verify if you can use setResultType: with NSManagedObjectIDResultType. This will return only the ID of the matching objects; if you need to access just one or a few of them after performing the fetch request, then the additional overhead is really small, but the fetch is much faster. If you need to access all of the returned objects, then this is not the way to go.
3) if you instead need to retrieve the properties stored in your objects, use setPropertiesToFetch: as suggested by Hunter to retrieve only the ones you really need
4) if your model includes subentities of your entity, then verify if you can use setIncludesSubentities: passing NO as argument.
5) if you do not need to deal with all of the objects matching the predicate associated to your fetch request, then use setFetchLimit: to retrieve a fixed number of objects.
Hope this helps.
Have you tried marking all editable elements indexed in the data model? As you said it seems really odd to index everything but Core Data should be smart enough to handle this appropriately.

Objective-C: Hierarchical passing of CoreData in NavigationController

I need some guidance with this issue I have. I have a navigational Controller, with the root controller making use of CoreData and an NSFetchController. Now, the root controller works fine. When I click on an item, hierarchically it should display the next UITable associating the values for the row in the previous root controller.
Now, I am not sure what I should pass as a property to the next UITableViewController. Should it be an array, or should I pass the NSFetchedResultsController? THere will be another level in the hierarchy after the second, as a point of note.
Thanks
Doron
You have a couple options, at least:
Pass a reference to the managed object controller from parent view controller to child view controller; or,
Make the MOC property available from the app delegate, which is available to any view controller
Some prefer the second option as you may reuse and rearrange view controllers into different hierarchies. By disconnecting the MOC from parent and child controllers, you gain a bit more design flexibility. Also, if your application needs to manage more than one persistent store concurrently, then getting access from one central point to make multiple MOCs reduces code complexity.
Once you have the MOC in its view controller, you can create your fetched results controller in that view controller and apply operations in their own threads.
EDIT
It sounds like you have a one-to-many relationship between Site and Post (i.e. one site entity has many post entities).
Your "Posts" list view controller could include a site managed object, siteID managed object ID, or a siteName string property.
When you push the "Posts" list VC, set its site or siteName property and configure the request predicate accordingly.
For example, your fetch request's predicate should include something like site.name LIKE '%#' in it, e.g.:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(site.name like '%#')", self.site.name]];
[fetchRequest setPredicate:requestPredicate];
// ...
Or you could compare on managed object IDs, which is generally better for unique comparisons:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(site = %#)", self.siteID]];
[fetchRequest setPredicate:requestPredicate];
// ...
In both cases, you have filtered your Core Data store to match all Post entities that have a site name or a site managed object ID equivalent to your criteria.
Once you perform the fetch, these entities generally reside in an NSSet* or NSArray* that you can easily use as a UITableView data store, especially if you are using a fetched results controller.