2010-01-08 3 views
11

У меня есть NSArrayController, companiesController, связанный с объектом Core Data верхнего уровня, Companies.Использование NSPredicate с основными данными для глубоких связей

A Company имеет большое количество Department, и Department имеет множество Employee; они представлены отношениями 1-ко-многим, departments и employees.

на основе атрибута salary в качестве Employee я думал, что я мог бы сделать это динамически для фильтрации на основе заработной платы внутри метода UI называемый:

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

Увы, это дает мне ошибку: -[NSCFSet compare:]: unrecognized selector sent to instance.

ответ

17

В этом случае не допускается использование нескольких ключей.

Вместо этого, вы можете сделать следующее:

  1. Изменить модель данных, добавив атрибут «фильтр» флаг (Boolean) к объекту Department.
  2. Создайте метод для: получения всех объектов Департамента, установите флаг фильтра в YES для отделов, отвечающих критериям второй половины вашего предиката, установите флаг фильтра НЕТ для других отделов и сохраните.
  3. Используйте флаг фильтра в предикате компании.

изменения кода (этап 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]; 

И новый метод (этап 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(); 
    } 
} 
+0

Спасибо, это работает. Похоже, это обходной путь, но это только проблемы с Core Data, я думаю. – raheel

+0

Я бы предположил, что это имеет какое-то отношение к тому, как Core Data переводит NSPredicate в инструкции SQLite. Синтаксис неоднозначен: вы имеете в виду ЛЮБОЕ или ВСЕ сотрудников (сотрудников) в конкретном отделе. Кроме того, я являюсь новичком SQL, но я предполагаю, что даже если ваш первоначальный предикат можно сделать в простом SQL-заявлении, для этого потребуется потенциально большое объединение, которое Apple может считать слишком большой или интенсивной для одной выборки. – gerry3

+0

Кроме того, я потратил немало времени на это, так что голосование будет оценено :-). – gerry3

10

Вы также можете сделать это с помощью подзапросов.

Получить все отделы. «От» отношения инверсией компании ко многим отделов:

-(void)printDepartmentsWithSalaryHigherThan:(int)salary inContext:(NSManagedObjectContext *)context {  
    NSFetchRequest *request = [[NSFetchRequest alloc ]init]; 
    request.entity = [NSEntityDescription entityForName:@"Department" inManagedObjectContext:context]; 
    request.predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(employees, $emp, $emp.salary > %@)[email protected] > 0", [NSNumber numberWithInt:salary]]; 

    for(Department *dep in [context executeFetchRequest:request error:nil]){ 
     NSLog(@"Department: %@", dep.depName); 
     NSLog(@"in Company: %@", dep.of.compName); 
    } 
    [request release]; 
} 

Или, если у вас есть больше компаний и просто хотят компании, которые имеют сотрудника с зарплатой «выше, чем» некоторое количество. Подзапрос, основанный на результате подзапроса

-(void)printCompaniesWithHigherSalaryThan:(int)salary inContext:(NSManagedObjectContext *)context { 
    NSFetchRequest *request = [[NSFetchRequest alloc ]init]; 
    request.entity = [NSEntityDescription entityForName:@"Company" inManagedObjectContext:context]; 
    request.predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(departments, $dep, SUBQUERY($dep.employees,$emp,$emp.salary > %@)[email protected] > 0)[email protected] > 0", [NSNumber numberWithInt:salary]]; 

    for(Company *c in [context executeFetchRequest:request error:nil]){ 
     NSLog(@"Company: %@", c.compName); 
    } 
    [request release]; 
} 
+0

Работает ли это с хранилищем SQLite для Mac OS и iOS? Из документации Apple (из библиотеки iOS 5.0: Руководство по программированию основных данных> Функции постоянного хранения> Извлечь предикаты и дескрипторы сортировки - могут отличаться для Mac OS): «Существуют дополнительные ограничения на предикаты, которые вы можете использовать с помощью SQLite store: Вы не можете перевести «произвольные» SQL-запросы в предикаты.« – Dalmazio

+0

Не знаю. Попробуйте: – andershqst

+0

Лучший предикат реального мира. Пример SubQuery. Я столкнулся с еще –