How to configure an NSFetchRequest to retrieve a to-many relationship - core-data

I've got a Core Data relationship set up like this:
Place PlaceList
===== =========
lists <<--------->> places
I'd like to populate an NSFetchResultsController with a given PlaceList instance's list of places. places is an ordered relationship, and lists is unordered. I'd like the fetch request to return the list of places in the order specified by the relationship.
I started out with this:
let request = NSFetchRequest<Place>(entityName: "Place")
request.predicate = NSPredicate(format: "lists == %#", placeList)
request.sortDescriptors = [NSSortDescriptor(key: "lists", ascending: true)]
When I fetch, though, no places come back. I've tried a bunch of ideas from other answers, trying all different predicates, and most result in exceptions thrown when the fetch request is executed.
I've tried, for example:
"ANY lists == %#"
"lists CONTAINS %#"
"%# IN lists"

Related

Custom ordering Core Data to-many relationship?

How can I sorting by a 'custom' order managed object in a to-many relationship? It seems managed objects are in an arbitrary order, and I need them ordered alphabetically by name property. Shall I extend managed object / entity?
Yes, managed objects in Core Data are in no particular order. You can order them when you fetch by setting the sortDescriptors property on the fetch request. If you are already doing a fetch, that is faster and more efficient than fetching first and then sorting in memory, for example, by calling sortedArrayUsingDescriptors.
So, if you were to fetch all Events, you would do as follows:
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Event.name),
ascending: true)]
Or, if you have a many-to-many relationship from Events to Users that is called usersWhoLiked, you could fetch all events that were liked by at least one user over age 50 as follows:
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "#"SUBQUERY(usersWhoLiked, $user, $user.age > %d).#count != 0",
50))
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Event.name),
ascending: true)]
But, note that the sortDescriptors apply ONLY to the fetch request's entity. And, if you are not doing a fetch at all, for example just ordering all the events of a particular user, then Jon Rose's solution may be just as efficient or more.
It's unclear what exactly you want to order. I hope this answers your question.
The relationship is defined as a set, which is unordered. You can turn the set into an array and then sort the array:
NSArray* sortedEvents = [user.events.allObjects sortedArrayUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]]]

NSFetchRequest to-many key not allowed here

I have a many to many relationship between two entities; A and B.
I want to return an array of B.relationship for every A where B.relationship’s count is greater than 0 and sorted by B’s dateCreated property.
This is the code I currently have which probably makes a little more sense.
let fetchRecentVariationsRequest = NSFetchRequest(entityName: "Variation")
fetchRecentVariationsRequest.predicate = NSPredicate(format: "ANY activities.#count > 0")
fetchRecentVariationsRequest.sortDescriptors = [NSSortDescriptor(key: "activities.dateCreated", ascending: true)]
When I run the request I get the following exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here’
I understand why I’m getting the error but how I sort by a to many property for a sort descriptor in Core Data?
Edit
To be more clear, I’d like to fetch the 6 most recent Activity entities sorted by their dateCreated property (newest first).
Then I’d like to fetch all of the Variation entities which are related to these fetched Activity entities via the Activity entity’s variations relationship.
You can't sort by the attribute of a to-many relationship, because it makes no sense. CoreData needs to decide which Variation to put first. Your sort descriptor says "use the value of dateCreated on the related Activities". But there are several Activities and so several different dateCreated values for each Variation. Which Activities' dateCreated should it use? The last? The first? The average?
But over and above that conceptual problem, CoreData will only allow you to use an attribute, or a to-one relationship, to sort by (at least for a fetch from the SQLite store). No transient properties; no computed properties. So if you want to use the dateCreated of the most recent related Activity, you will need to add an attribute to Variation which you update every time an Activity is added to the relationship.
EDIT
Given your update, I would fetch the most recent six Activities first:
fetchRecentActivitiesRequest = NSFetchRequest(entityName: "Activity")
fetchRecentActivitiesRequest.sortDescriptors = [NSSortDescriptor(key: "dateCreated", ascending: false)]
fetchRecentActivitiesRequest.fetchLimit = 6
// I recommend using the next line to fetch the related Variations in one go
fetchRecentActivitiesRequest.relationshipKeyPathsForPrefetching = ["variations"]
let recentActivities = try! context.executeFetchRequest(fetchRecentActivitiesRequest) as! [Activity]
and then use the variations relationship to get the corresponding Variations:
let nameSort = NSSortDescriptor(key: "name", ascending: true)
let recentVariations = recentActivities.flatMap() {
$0.variations!.sortedArrayUsingDescriptors([nameSort])
}

how to create compoundpredicate within nsFetchRequest that filters by two parameters of different entity

Some background info on my datamodel:
manufacturer <-->> item <<-->> tag
I currently generate a list of items by a fetchrequest:
- (NSFetchRequest*) rankingRequestForItem:(Item*)item {
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
NSPredicate* p = [NSPredicate predicateWithFormat:#"SELF != %#",item.objectID];
r.resultType = NSDictionaryResultType;
r.resultType = NSDictionaryResultType;
r.propertiesToFetch = #[[self objectIDExpressionDescription],#"itemName",
[self rankingExpressionDescriptionForTags:[item mutableSetValueForKey:#"itemToTag"]]];
r.predicate = p;
r.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"itemName" ascending:YES]];
return r;
}
This generates a list of all items. I want to filter it for items that have a relationship to a specific manufacturer. So I'm adding a predicate after the listing of all items and it sorts by selectedManufacturer.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"itemToMa = %#", selectedManufacturer];
This works, but is grabbing a lot of items that will be filtered out. With large data sets I'm assuming will become slower and slower as it searches all items rather than just the ones associated with one manufacturer. I want to filter for items within the initial 'rankingRequestForItem' method.
Is it possible to move the above predicate with the top predicate and create a compoundpredicate?
I would not worry about performance. Core Data manages that pretty well under the hood. Sometimes the order of the predicates matters, so maybe put the manufacturer filter first.
You can combine the predicates in one as suggested in the comment to your question, or use compound predicates -- the result is pretty much the same.

Evaluate CoreData entity type in predicate

I have a block predicate that I carefully crafted only to discover you can't use them in Core Data.
NSPredicate *rootContactPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
BOOL isPersonAndRoot = ([[[evaluatedObject entity] name] isEqualToString:#"Person"] && [[(Person*)evaluatedObject accounts] count] == 0);
BOOL isAccountAndRoot = ([[[evaluatedObject entity] name] isEqualToString:#"Account"] && [(Account*)evaluatedObject root] == nil);
return isPersonAndRoot || isAccountAndRoot;
}];
So I need to convert this into a standard String format predicate, but I am unclear on how to check the entity type for the evaluated object. The Person and Account entities are subclasses of a Contact entity which is the type being evaluated in the fetch request. I'm hoping it will see the sub-types.
Check the entity like this:
[NSPredicate predicateWithFormat:#"self.entity = %#", Person.entity];
Swift 4.0 way of checking for entity type using the entity property of the NSManagedObject as per malhal's suggestion.
let entityDescription = NSEntityDescription.entity(forEntityName: "Account", in: managedObjectContext)!
return NSPredicate(format: "entity = %#", entityDescription)
It seems that now you can now simply compare the entity in a predicate, supplying the managed objects entity as the value:
"entity = %#"
Previously:
You can't. The reason is that the predicate will need to be converted so that it can be run on the underlying data store (presumably SQLite). The SQLite database doesn't have any data on the type of the element, it only knows about the keys and values of the objects.
Depending on what you're trying to do, you'll either need to run a single fetch request against the keys known in the super entity. Or you'd need to have 2 fetch requests, separately executed and then combine the 2 result sets.

Filtering a relationship of an NSManagedObject

Suppose a Manager has a to-many relationship with Employee objects. Given a reference to a Manager object (i.e. NSManagedObject *manager), how do I get a reference to "the Employee with the lowest salary among all of those whose salaries exceed 10000"?
I can think of two possible approaches:
Approach 1: constructing an NSFetchRequest and specifying the Manager wanted with the object ID of the Manager in question.
Approach 2: some kind of key-value coding expression on Manager, e.g. [manager valueForKeyPath:#"..."] (with some use of NSPredicate?)
I'm inclined towards Approach 2 if there's a plausible solution. Please enlighten me.
Of course, you can just apply a predicate and sort descriptor to the set returned by the relationship. Easy, and pretty quick if the set is relatively small (because it is done in memory, all the objects will have to be fetched). You may want to do that batch fetch up front to limit the number of times you do I/O.
Depending on the size of the database and the number of employees under the manager (and the indexing), you may want to do it all at the database level...
// We want "Employee" objects
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Employee"];
// Assuming the "inverse" relationship from employee-to-manager is "manager"...
// We want all employees that have "our" manager, and a salary > 10000
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"(manager == %#) AND (salary > 10000", manager];
// Sort all the matches by salary, little-to-big
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"salary" ascending:YES]];
// Limit the returned set to 1 object, which will be the smallest
fetchRequest.fetchLimit = 1;
This will perform the entire query at the database level, and only return 1 object.
As always, performance issues are usually highly dependent on your model layout, and the options used for specifying the model and its relationships.
You can filter your array of Employee relationship to get the one you want.
1) First, get all the Employee with salaries over 10000:
NSArray *filtered = [manager.employees filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(salary > 10000)"]];
2)Then sort it in descending order
NSSortDescriptor* sortOrder = [NSSortDescriptor sortDescriptorWithKey: #"salary" ascending: NO];
NSArray *sortedAndFiltered = [filtered sortedArrayUsingDescriptors: [NSArray arrayWithObject: sortOrder]];
3)Then just get your employee
[sortedAndFiltered lastObject];

Resources