I am creating an app that navigates through multiple levels of one-to-many relationships. So for example, pretend that the CoreDataBooks code sample starts with a list of genres, you click on a genre and then get the list of books organized by author as seen in Apple's code sample.
Here is my problem: the Apple documentation tells me I should use a FetchedResultsController to help organize my list of books into sections (among other reasons). But when trying to figure out how to get from "one" genre to my "many" books, the Core Data FAQ tells not to use a fetch. From the FAQ:
I have a to-many relationship from Entity A to Entity B. How do I fetch the instances of Entity B related to a given instance of Entity A?
You don’t. More specifically, there is no need to explicitly fetch the destination instances, you simply invoke the appropriate key-value coding or accessor method on the instance of Entity A.
The problem, of course, is I now have my books in a set, but I want them to get them from a fetched results controller.
What is the best way to proceed here? Should I follow the FAQ, and if so, how do I manage dividing my books up into sections by author?
Or do I use a fetched results controller (which I suspect is better), in which case how do I traverse the one-to-many relationship (since Apple's oh-so-helpful answer is simply "don't")?
Many thanks for your help.
Sasha
You have a data model that looks roughly like this:
Genre{
name:
books<-->>Book.genre
}
Book{
name:
genre<<-->Genre.books
}
In your master table, you run a fetched results controller to get table of Genre objects. Then the user selects one of the row which behind the scenes selects a particular Genre object.
Since every Genre object has a books relationship that points to the related Book objects, you have automatically got a reference to all the related book objects so you don't have to fetch anything. For your book tableview you just create a sorted array of the Book objects in the selected Genre object's books relationship.
Think of a Core Data object graph as a clump of bead strings all woven together in a web or fabric. The beads are objects and the strings are relationships. A fetch plucks one of the bead/objects from the clump but once you have that bead/object in hand, then you can just pull on its string/relationship to pull out all the beads/objects related to the bead in your hand.
So, fetches are used in most cases just to find the starting objects, then you walk relationships to find most of the other objects.
That is why the Apple docs say you don't need a second fetch.
Related
I recently asked this question about how best to retrieve and display in a tableview the titles my FRC is using for section headers.
Following a line of research suggested by #Mike Pollard in the second answer to my question, I ran across this question and was immediately struck by the similarity to my situation, and by the 4th answer, posted by #aroth.
Aroth's approach certainly appears sound, and I've created a new Category entity. Specifically, Category has a to-many relationship with Item, and Item has a to-one relationship with Category. However I'm having trouble understanding one aspect implicit in his proposed solution, and, more fundamentally, in this relationship:
In my case, both Category(s) and Item(s)--"Item" is called "ListActivity" in my case, but "Item" will do for illustration purposes-- will be named via two corresponding user input fields, which seems like it could result in multiple entries of the same name in the Category list.
My question:
How can I ensure that when I fetch a list of Categories that I get a singular instance of each category, i.e., one category per row in the tableview, with no repeats? Will Core Data automatically assign each new incoming Item to a singular instance of the appropriate Category via the relationship? Or will it somehow test for and winnow the list down to one entry per Category name upon receiving the fetch request? Or must the filtering be done with a predicate in the fetch request?
Thanks!
Core Data will do what you tell it to. This sounds like an issue related to you creating content in your data store rather than an issue with the FRC and table view. It's your responsibility to search for and reuse existing objects rather than creating duplicates and adding them to the store - indeed, only you (your code) knows what constitutes a duplicate.
So, basically, as you create new items, use a fetch request and predicate to find the suitable existing category (or suggest categories based on partially entered names). Then, either connect to the existing category or create a new one.
I'm struggling with creating a suitable Core Data model for my app. I'm hoping someone here can provide some guidance.
I have two entities -- "Goals" and "Items". The Goals entity contains only a goal description, but any goal may have any number of subgoals, and these may extend multiple levels in a tree structure. Subgoals are to be contained within the same entity, so presumably the Goal entity will contain a pointer to "parent" which will be the parent goal of any subgoal.
There will also be an "Items" entity that contains a couple of text fields and a couple of binary items, and must be linked (ideally, by a unique identifier, perhaps objectID) to the particular goal or subgoal the item(s) are related to.
I am totally fumbling with how to set this model up. I know what attributes need to be in each entity, but the relationships, particularly between goals and "subgoals", has me stumped. I don't seem to be able to turn up any good examples of tree structures in Core Data on the Internet, and even the couple of books I have on Core Data don't seem to address it.
Can anyone here help an old SQL programmer get headed the right direction with these relationships in Core Data? Thanks.
Have you tried creating a one-to-many from Goal to itself, and a one-to-one from Goal to Item? The only thing I would worry about here is circular references.
Also, read Relationships and Fetched Properties in the CoreData Programming Guide.
Here is how it is done:
You set up a to-many relationship from Goal to Item in the model editor. Don't use any ids, foreign keys etc. This is old-fashioned database thinking - you can forget about it. Here we are only dealing with an object graph. The database layer is just an implementation detail for persisting the data.
Make two more relationships in entity Goal to itself: a to-one called parent, a to-many called subGoals. Make them the inverse of each other. Simple!
QED is correct, you can create a to many relationship on goal (call it subgoals) as well as a to-one relationship on goal (call it parentGoal) and set them as inverses to each other.
Then create another to many relationship (call it items) on the goal entity, with the inverse being a to one relationship on the item entity (call it goal). Then you're all set. You don't need to link items with a unique id, just add them to the items relationship.
Also note that if you did want to give items a unique id, do not use the objectID. The objectID should only be used as a temporary id as they are not guaranteed to remain the same. In fact they will change if you ever do a Core Data migration.
One way, though not really great, is to create a another entity, say subGoal, and each goal has one subGoal and each object of subGoal has many goal.
I know union is a SQL construct, but it's the best analogue for what I'm trying to do.
I have multiple groups of data that I'm receiving from an external source. I'm maintaining them as separate entities in Core Data (they only have some attributes in common (e.g. name)), but I want to present them in the same tableView.
Say I have an entity Food that has relationships with FruitGroup and VegetableGroup. The FruitGroup has a relationship with Fruit which has a relationship with FruitType. The VegetableGroup is similar.
How can I use FruitGroup.Fruit.name and VegetableGroup.Vegetable.name as sectionTitles? And FruitGroup.Fruit.FruitType.name and VegetableGroup.Vegetable.VegetableType.name for row data. (I tried coming up with a predicate that walks down from Food, but that doesn't appear to be workable)
Example modeled data (my groups are far more disparate than fruits and veggies, so re-doing my data model is not an option):
Food
FruitGroup
Apple
Macintosh
Granny Smith
Pear
Bartlett
Asian
Anjou
VegetableGroup
Asparagus
white
wild
Peas
baby
split
Which I would like to appear as:
Apple [section]
Macintosh [row]
Granny Smith
Pear
Bartlett
Asian
Anjou
Asparagus
white
wild
Peas
baby
split
I could use multiple NSFetchedResultsControllers in the UITableViewController and conditionally select the FRC within each of the UITableViewDataSource methods, but that doesn't feel clean.
I'm thinking about subclassing NSFetchedResultsController and, internal to my subclass, merging the results of multiple private NSFetchedResultsControllers that each represent one of the entities. (e.g. sections returns a concatenation of the returns from the sections calls of the internal FRCs)
Does this make sense - or is there a better way? (I saw Core Data Union Query Equivalent but since there are relationships among my entities, I wanted to seek alternatives)
While you can do this as described in the other answers (via creating an abstract Parent entity), I would not recommend it. The performance when it comes to dealing with abstract parents gets bad very quickly. The reason for this is that Core Data will put all of the children into a single table in the underlying SQLite file.
I would suggest going a different route. Have a single entity called Food with attributes describing if it is a vegetable or fruit. Then you have one NSFetchedResultsController which has the type of the food item as the sectionPath and you will get your display the way that you want it.
I recommend creating entities in Core Data based on what the objects are as a very loose level. I would not create entities for Honda, Ford and Dodge, but create an entity for Car and perhaps type or a relationship to a manufacturer.
While Core Data can be backed by a database, at the end of the day it is not a database but an object graph and should be treated as such. Trying to normalize the database will result in poor performance of the object graph.
You should probably look into abstract entities. For example, you could create an abstract entity called Food. Then you're able to create Fruit and Vegetables, which inherits the abstract entity. You'll have to set Food as the "Parent Entity".
Then you could fetch all the items with the entity Food, which includes both Fruit and Vegetables. Based on your post, you'll probably will have a relation from Food to FoodGroup.
To answer your question:
You cannot unify different entity types (if they are not subclasses of the same entity) under a single fetch request. You can define an entity (B) to inherit from another entity (A) and then fetch by the parent entity (A) and get both kind of entities (As and Bs)
You can try and think of it this way:
Item ("Macintosh","White Asparagus",...) has a relationship to Group ("Apple","Asparagus",...), and Group has a relationship to Area (or simply to another parent group).
In this manner you could use a single FRC with sectionNameKeyPath of "group.name" and entity Item (you can filter by "group.area" to only select food items).
In Core Data of Xcode 4.3.2, an Ordered To-Many relationship is modelled with NSOrderedSet. It works well until I found the need to have repeated items in the relationship; it should really be modelled in a NSArray.
For example, in a music app, I have the following songs: SongA, SongB, SongC ,
I may want a party play list where people insert songs in any order and could be repeated. The list may look something like:
[SongC, SongC, SongA, SongC]
The way Core Data currently works, the list would become:
[SongC, SongA]
I.e., all repeated items are dropped, as it is modelled with sets.
So, coming back to my question: what is a good way to model repeated items in order in a relationship in Core Data?
Relationships are sets (and ordered relationships are ordered sets, but still sets); sets by definition contain unique objects. So you can't put duplicate objects into a relationship either way.
Whether you use the ordered-relation feature or not, you'll want to go back to the abstract ER model to find another way to turn your conceptual relationships into a Core Data model... it might help to think about how you'd do it in a plain SQL (or SQL-like) database and then come back to what Core Data does beyond SQL.
It sounds like you're making something akin to iTunes playlists, no? A model that might work for that would go something like:
Playlist <--->> PlaylistEntry
PlaylistEntry <<---> Song
The PlaylistEntry entity represents one instance of a Song's inclusion in one Playlist. You can have multiple PlaylistEntrys that reference the same Song in a single Playlist, and you can add other attributes to the PlaylistEntry to keep track of other things (like song order, if you're not using an ordered relationship). As a bonus, you can use that to add other features if you like -- say, to make a playlist that plays three different snippets out of one long track.
As rickster said, relationships as managed by Core Data uniquely associate entities between them. Even if Lion's Core Data (is supposed to) supports ordered relationships (supposed to, because in practice it won't work, the feature is buggy, barely usable), they still are relationships that follow the relational database model.
And so you have to manage the association by yourself, and you most certainly have to manage the ordered part of the association by yourself too.
Score <->> ScoreSong
ScoreSong <<-> Song
With ScoreSong having the following properties:
ScoreSong:
- score: -> Score
- song: -> Song
- order: integer, indexed
Then you have to use a Fetch Request with a sort descriptor for the key order, which will return an ordered NSArray of ScoreSong. You can ask the fetch request to prefetch the songs, then you can create the songs array, still properly ordered, with a single call to valueForKey: #"song".
Of course you have to create different ScoreSong for a single Song when you need to include than song more than once in your Score. That's the whole point of the added indirection.
My question is similar this one here:
ResultsController to another ResultsController
A typical app structure on ios is to drill down into data via table views, and plenty of app models are hierarchical. For example, a Film Festival could have many Films, which could have many Screenings, which could have many Attendees. If we use Core Data to represent this model, then we can use an NSFetchedResultsController to populate Films into a UITableView. Using the NSFetchedResultsController helps greatly with performance and memory efficiency, and provides built-in support for observing changes to the underlying data. I would like to take advantage of this in my project as much as I can.
So if we have a table of Films backed by this sweet NSFetchedResultsController, and the user selects a Film to see a list of its Screenings, we can pass that Film (a subclass of NSManagedObject) to a new UITableViewController and populate that table with the film's screenings.
The core of my question is not "How do I do this?" Instead, it's asking if the benefits of NSFetchedResultsController travel along with the NSManagedObject. I could build a new results controller using the event in the predicate, but I don't need to. If I pass the Film object into a variable called film, and my to-many relationship is identified as screenings, then I believe I can get the set of screenings related to that film like this:
NSSet *filmScreenings = [film screenings];
If I turn that set into an array, and use that as the data backing my new table view of screenings, am I losing the benefits of the NSFetchedResultsController? My gut tells me yes, especially the support for monitoring changes - but an FAQ in Apple's docs made me second guess and ask the community at large. Check out the question in this FAQ called "I have a to-many relationship from Entity A to Entity B..."
Should I use that accessor method then, or should I build a new NSFetchedResultsController?
You will lose the benefits of NSFetchedResultsController by doing this (e.g. batch fetching and change monitoring), as you're going to replace it with a simple NSArray. You're best bet is to pass your Film to the details controller, and construct an NSFetchedResultsController using this Film in the predicate for the NSFetchRequest.