Sort related entities in core data - ios

I have two entities in my core data model. Author and Book. An author can have many books.
This is the part I've got sorted out. It's all working.
Now I'd like to fetch my authors in alphabetical order:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Author" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
No problems so far. But now I would like to fetch all the author's books, ordered by release date.
When I fetch the author's books using the following line of code …
self.selectedAuthor.books.allObjects
… the order of books seems to be completely random.
My question:
How can sort an object's related entities when fetching them from the core data, so that every time I use self.selectedAuthor.books.allObjects, all books are ordered by (for example) release date?

You can add a method - (NSArray *)booksSortedByDate; to the Author class interface and in the implementation write code like this:
- (NSArray *)booksSortedByDate {
return [self.books.allObjects sortedArrayUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"releaseDate" ascending:YES]]];
}

You can't. The to-many relationship books is represented by an NSSet, which is an
unordered collection. You can either
Get the array of all books self.selectedAuthor.books.allObjects as you did,
and then sort this array in memory,
or
Execute a fetch request on the Book entity with the predicate
[NSPredicate predicateWithFormat:#"author = %#", self.selectedAuthor]
to get only books of that author, and add a sort descriptor to the fetch request
to get the books sorted by release date.

Related

NSFetchedResultsController with data from other model

I'm trying to build app with UITableView with data from NSFetchedResultsController. The problem is - I need to show some additional data from other models in table view.
Assume I have 2 models: Students and Groups (I think the relation is obvious: group consists of many students and student can be only in one group). I'm trying to build UITableView with list of groups. I would also like number of students in each group.
(NSFetchedResultsController *) fetchController {
if (!_fetchController) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"GroupModel" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]];
[fetchRequest setEntity:entity];
NSPredicate *messageInChatPredicate = [GroupModel getMyChatsPredicate];
[fetchRequest setPredicate:messageInChatPredicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
_fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:#"main"];
_fetchController.delegate = self;
}
return _fetchController;
}
I wonder how I can I add additional information (such as number of students in each group)?
Why not just use group.students.count to get the value from the group entity? You only need one fetchedResultsController and I think you will find Core Data performance is fine.
Your terminology is a bit confusing, usually a model is a core data model created in Xcode and has a number of entities e.g. Group, Student. Sometimes you can use two different models and/or different versions of the models.
Your objects are usually know as Entities and both would belong to the same Core Data model.
EDIT:
To get the count of subgroups, such as girls or boys you can do the following
NSMutableArray *boys = [[NSMutableArray alloc] init];
[NSPredicate predicateWithFormat:#"gender = %#",#"MALE"];
[boys addObjectsFromArray:[group.students filteredArrayUsingPredicate:predicate]];
NSLog(#" boys count is %d", boys.count);
Assuming of course that gender is an attribute of Student.
Create another NSFetchedResultsController that is responsible for the other model (i.e student).
You have basically two options to get additional information such as the number of students in each group.
The first option is to count the students in the Group-Student relation. To serve this request the NSManagedObjectContext will load all students in memory to get a suitable result. Personally, I don't really like this option.
The second option is to store the number of students who belong to a group in a property of the group so that it can be directly add. Yes, you have to maintain the correct number manually. Depending on the amount of data that has to be loaded, this approach is more preferable. Crucially since it's way fas

Core Data fetching many-to-one relationship objects

In my app I have two entities which are Items and Lists. Each item belongs to only one list and each list has many items. Thus in the modal the entities has the following relationships:
For Items: to-one relationship (belongs_to_list) pointing to one list
For Lists: to-many relationship (has_items) pointing to many items
How can I fetch the items using a predicate to check whether the list it's related to is equal to a specific list I provide? I don't want to fetch the items through the list (like fetching objects of has_items). I want to be able to use belongs_to_list in a predicate to compare it to a managed object I have. I tried the following but it's not working. Any help please?
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Items" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"item_detail" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"list.list_name == %#", [self.currentList valueForKey:#"list_name"]];
[fetchRequest setPredicate:predicate];
If I understood well your question, the following predicates would be correct:
[NSPredicate predicateWithFormat:#"belongs_to_list == %#", [self currentList]];
Let me know if this does work for you.

NSFetchRequest / NSFetchedResultsController returning single property distinct but sorted using another attribute of the entity

I am using an NSFetchedResultsController to display data in a table. The data store contains lots of rows that have a groupID, and multiple entities can share a groupID.
Each entity has an insertion date property as well.
I would like to get a list of distinct groupID ordered by the insertion date property. The following code works, except it is not sorted by the insertion date property. I assume that since that property is not one of the ones being fetched, it is not available for sorting.
And I am using MagicalRecord as well, fwiw.
Is there a way to use other properties of the entity for sorting but not have them as part of the result? Adding in the insertion date to the fetched properties makes distinct superfluous since the dates are all unique as they are.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userID like[c] %#", THEUSERUSERID];
NSFetchRequest *fetchRequest = [AnEntity MR_requestAllWithPredicate:predicate];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:#[#"groupID"]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"inDate" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
_frController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_rootSavingContext] sectionNameKeyPath:nil cacheName:nil];
Chadbag ... I had the same problem. I researched the setPropertiesToFetch and found out it requires an NSArray of NSPropertyDescriptions ... not just the attribute name.
I think if you REMOVE this:
[fetchRequest setPropertiesToFetch:#[#"groupID"]];
and ADD this:
NSDictionary *entityProperties = [entity propertiesByName];
NSPropertyDescription *propDescription = [entityProperties objectForKey:#"groupID"];
NSArray *propArray = [NSArray arrayWithObject:propDescription];
[fetchRequest setPropertiesToFetch:propArray];
That should work for you ... it did for me!

Core Data and NSSortDescriptor not sorting according to third descriptor based on NSString

I have an NSFetchRequest that is fetching an entity called "Player" and I want the results be sorted by 3 attributes in the following order:
Whether the attribute called "sendingoffOffence" is nil or not
Whether the relationship in the entity at "team.homeMatch" is nil or not
I want the strings in the "number" attribute to be sorted in ascending order.
Basically, I am looking to have all players that have a red card ("sendingOffOffence" is not nil) appear at the top of the list, then have the set be ordered by whether the player is on the home team or not, and then finally get the set of players on a team in an offense group be sorted by their jersey numbers.
As such, I use the following code in my fetch request:
// Set the entity for the fetch request
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Player"
inManagedObjectContext:self.match.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"match == %#", self.match];
[fetchRequest setPredicate:predicate];
// Sort the results
// Sort according to whether the user has a red card / sendingOffOffence
NSSortDescriptor *hasRedCard = [[NSSortDescriptor alloc] initWithKey:#"sendingoffOffence" ascending:NO];
// Sort according to whether the user is on the home team or not
NSSortDescriptor *isHomeTeam = [[NSSortDescriptor alloc] initWithKey:#"team.homeMatch" ascending:NO];
// Sort according to the player's jersey numbers
NSSortDescriptor *number = [[NSSortDescriptor alloc] initWithKey:#"number" ascending:YES selector:#selector(localizedStandardCompare:)];
[fetchRequest setSortDescriptors:#[hasRedCard, isHomeTeam, number]];
// Set the amount of records to retrieve from the database
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.match.managedObjectContext
sectionNameKeyPath:nil
cacheName:#"MatchCache"]
However, when I execute the above code, I get the following results:
This sorting order is wrong because I want the following order to appear in the red card section:
11 - Home
12 - Home
0 - Away
11 - Away
21 - Away
And in the yellow card section:
1 - Home
2 - Home
99 - Home
1 - Away
31 - Away
It looks like the yellow card section sorts correctly but the red card section is showing very weird behavior that makes it appear that it is not getting sorted at all.
I am fairly stumped as to why the red card section fails to sort correctly - any thoughts? Should I just sort these objects in memory instead of relying on core data to get my preferred order?
Please be aware that this is a core data application with a SQL backed persistence store.
UPDATE
The following is the SQL statement that core data is using in my fetch:
CoreData: annotation: fetch using NSSQLiteStatement <0x11c77cd0> on entity 'SPlayer' with
sql text 'SELECT t0.Z_ENT, t0.Z_PK FROM ZFSMANAGEDOBJECT t0 LEFT OUTER JOIN ZSTEAM t1 ON
t0.ZTEAM = t1.Z_PK WHERE ( t0.ZMATCH1 = ? AND t0.Z_ENT = ?) ORDER BY
t0.ZSENDINGOFFOFFENCE DESC, t1.ZHOMEMATCH DESC, t0.ZNUMBER COLLATE NSCollateFinderlike '
This is the line that is not being properly applied (for the RED card case):
NSSortDescriptor *isHomeTeam = [[NSSortDescriptor alloc] initWithKey:#"team.homeMatch" ascending:NO];
Without seeing your data model and some of the sample data, it's hard to say why this would not be sorting properly. I would assume this would put NIL values first, then proper values next (so sorting ascending NO would do what it's doing in the YELLOW card case).
If I was in this situation, I would check my assumptions about sort order and these team.homeMatch properties. Do a test where this is the only sort description.
Then focus in on what is different with the RED/YELLOW card condition.
We done without NSFetchedResultsController like this check it....
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Player"
inManagedObjectContext:self.match.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"match == %#", self.match];
[fetchRequest setPredicate:predicate];
// Sort the results
// Sort according to whether the user has a red card / sendingOffOffence
NSSortDescriptor *hasRedCard = [[NSSortDescriptor alloc] initWithKey:#"sendingoffOffence" ascending:NO];
// Sort according to whether the user is on the home team or not
NSSortDescriptor *isHomeTeam = [[NSSortDescriptor alloc] initWithKey:#"team.homeMatch" ascending:NO];
[fetchRequest setSortDescriptors:#[hasRedCard, isHomeTeam]];
// Set the amount of records to retrieve from the database
[fetchRequest setFetchBatchSize:20];
NSArray *resultArray = [self.match.managedObjectContext executeFetchRequest:request error:&error];
NSSortDescriptor *number = [[NSSortDescriptor alloc] initWithKey:#"number" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortedArray = [resultArray sortedArrayUsingDescriptors:[NSArray arrayWithObjects:number, nil]];

NSFetchedResultsController and Entity Inheritance

I have a parent entity in my model Event. And two child entities: Birthday, Anniversary. I'm using the entity inheritance feature built into Core data such that birthday and anniversary's parent object is Event.
So I do a fetch using the following:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"start_date" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
etc...
Now I want to sort the fetchedObjects by event type, birthday or anniversary.
How do I do that? I tried:
for (Birthday *birthday in _fetchedResultsController.fetchedObjects){
[birthdayObjects addObject:birthday];
}
for (Anniversary *anniversary in _fetchedResultsController.fetchedObjects){
[anniversaryObjects addObject:anniversary];
}
But this just adds all objects in the fetchedObjects to each array.
Any ideas or am I going about this the wrong way?
UPDATE:
Figured it out using this:
for (Event *event in _fetchedResultsController.fetchedObjects){
if ([event isKindOfClass:[Birthday class]]){
[birthdayObjects addObject:event];
}else{
[anniversary addObject:event];
}
}
But if there is a better way I am open to it! Thanks.
Have a read-only property on Event, called eventType or similar, which returns a string. Your Event class returns one string, and your child events return Birthday, Anniversary, whatever else for future event types.
In your fetched results controller, if you use one, you can set eventType as your section name key path and this will divide the types into sections for you - transient properties like this are fine for this purpose, but you can't use them as part of the fetch request.
In the situation above, use the event type as a predicate or sort descriptor to process the array of all events that comes back from the fetch request.
If you don't like a string, you could use an enum. If you want to use the event type in a fetch request, make it an attribute of the Event class and set it appropriately on awakeFromInsert.

Resources