views:

976

answers:

1

I have an NSArrayController, companiesController bound to a top level Core Data entity, Companies.

A Company has many Department's, and a Department has many Employee; these are represented by the 1-to-many relationships, departments and employees.

Based on the attribute salary of an Employee I thought I could dynamically do this for filtering based on salary inside a UI-called method:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY departments.employees.salary < %@", [NSNumber numberWithInt:23000]];
[companiesController setFilterPredicate:predicate];

Alas, this gives me the error: -[NSCFSet compare:]: unrecognized selector sent to instance.

+9  A: 

Multiple to-many keys are not allowed in this case.

Instead, you could do the following:

  1. Modify the data model by adding a "filter" flag (Boolean) attribute to the Department entity.
  2. Create a method to: fetch all the Department objects, set the filter flag to YES for the departments that meet the criteria of the second half of your predicate, set the filter flag to NO for the other departments, and save.
  3. Use the filter flag in the Company predicate.

Code changes (step 3):

    //NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY departments.employees.salary < %@", [NSNumber numberWithInt:23000]];
    [self setDeptFilter:23000];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY depts.filter == YES"];
    [companiesController setFilterPredicate:predicate];

And the new method (step 2):

- (void)setDeptFilter:(NSUInteger)salary {
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Department" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    NSError *error = nil;

    // fetch all Department objects
    NSArray *array = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

    [fetchRequest release];

    if (error) {
        NSLog(@"Error fetching Departments %@, %@", error, [error userInfo]);
        abort();
    }

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY emps.salary < %@",[NSNumber numberWithInteger:salary]];
    NSArray *filterArray = [array filteredArrayUsingPredicate:predicate];

    // set filter flag to YES for the departments that meet the criteria
    for (Department *dep in filterArray) {
        dep.filter = [NSNumber numberWithBool:YES];
    }

    NSMutableArray *diffArray = [array mutableCopy];
    [diffArray removeObjectsInArray:filterArray];

    // set filter flag to NO for the departments that do NOT meet the criteria
    for (Department *dep in diffArray) {
        dep.filter = [NSNumber numberWithBool:NO];
    }

    [diffArray release];

    // save
    if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    } 
}
gerry3
Thanks, this works. Does seem like a workaround, but that's just Core Data's issues I guess.
raheel
I would assume it has something to do with how Core Data translates the NSPredicate into SQLite statement(s). The syntax is ambiguous: do you mean ANY or ALL Employee(s) in a particular Department. Also, I am a SQL novice, but my guess is that even if your original predicate can be done in a straightforward SQL statement, it would require a potentially large join which Apple may consider too memory or performance intensive for a single fetch.
gerry3
Also, I spent quite a while on this so an up-vote would be appreciated :-).
gerry3