views:

176

answers:

1

Here is the model I have: http://www.girardet.ch/model.png

My goal is to retrieve all the Quotes with these criterias:

  • belong to a specific theme : the name_en attribute of Themes
  • order by relevancy
  • filtered by Authors (with the alias attribute of Authors)

Here is my code:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"ThemeEntries" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"relevancy" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

// predictate - filter
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"theme.name_en=%@ AND quotes.author.alias=%@",@"mytheme", @"myauthor"];

[fetchRequest setPredicate:predicate];

I get the "to-many key not allowed here" error.

If I instead use this predicate

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"theme.name_en=%@, @"mytheme"];

it works well and I can loop over the ThemeEntries that I get and get all my quotes... But it's not filtered by authors.

What can I do to filter by Authors?

+3  A: 

Your problem is that the relationship of one of you keypath is a to-many, to-many and the predicate does not know which particular object goes with which.

You have ThemeEntities<<-->>Quotes which is produces a set at each end. The quotes.author.alias keypath says "a set of quotes instances, each linked to author instances which in turn has an alias attribute." The predicate cannot process the set.

You need to use a subquery to jump a to-many keypath. The subquery is essentially a nested predicate which searches a set and returns another set of objects matching the nested predicate. Subqueries are poorly documented but they have the form:

SUBQUERY(collectionName,$collectionElementVariable,expression-with-$collectionElementVariable)

In this case, you are looking for any quote instances that has a author relationship with an alias matching the supplied string. Your predicate would need to look like:

@"theme.name_en=%@ AND (0!=SUBQUERY(quotes,$eachQuote,$eachQuote.author.alias=%@).@count)",@"mytheme", @"myauthor"

The subquery says, "Of the set of quotes, take each quote object and test if its author relationship object has an alias attribute matching 'myauthor'. Count the number of quote objects with that match. If the number is non-zero, return TRUE."

You need to use subqueries whenever you walk a keypath across a to-many relationship.

TechZen
Thanks heaps for this great answer. I didn't know about the subqueries and it's definitely very helpful. In my case however I wanted to filter the set of quotes returned by the request "inside" all the ThemeEntries. Using the subquery can filter the returned entries, making sure that they have a relationship with the author through the quotes they are related to. What I finally did is getting ALL the quotes related to the theme without filtering the author and filtered them afterward. But I still wonder if there is a way of doing it directly in the request (like a left join would do in sql)
Ben
Not exactly sure what you are looking for but the above predicate only searches those `Quote` instances linked to specific `ThemeEntities` instances. In fact, owing to how predicates are evaluated, it will only check those `Quote` instances for `ThemeEntities` that pass the first test.
TechZen