Динамическое создание предиката с различным количеством атрибутов и значений

У меня есть объект с тремя атрибутами: studyID: строка, studyName: строка и studyDate: дата.

Мне нужно создать предикат для поиска, но у пользователя есть возможность ввести любую возможную комбинацию всех трех атрибутов, например. один поиск выполняется по studyID и studyDate, но не по studyName, а следующий поиск выполняется только по studyID.

Как создать предикат динамически в зависимости от того, какую комбинацию атрибутов пользователь решает искать?


person Ali    schedule 25.03.2011    source источник


Ответы (2)


Вам нужно создать составной предикат, окончательная форма которого зависит от того, по каким атрибутам пользователь ищет.

Самое чистое решение — создать предикат в коде, используя NSExpression, NSComparisonPredicate и NSCompoundPredicate. Это позволит вам настроить предикат для каждого поиска.

Что-то вроде этого:

-(NSPredicate *) predicateWithStudyID:(NSString *) aStudyID studyName:(NSString *) aStudyName date:(NSDate *) aDate{
  NSPredicate *p;
  NSMutableArray *preds=[[NSMutableArray alloc] initWithCapacity:1];
  if (aDate != nil) {
    NSExpression *dateKeyEx=[NSExpression expressionForKeyPath:@"studyDate"];
    NSExpression *aDateEx=[NSExpression expressionForConstantValue:aDate];
    NSPredicate *datePred=[NSComparisonPredicate predicateWithLeftExpression:dateKeyEx
                                                             rightExpression:aDateEx 
                                                                    modifier:NSDirectPredicateModifier
                                                                        type:NSEqualToPredicateOperatorType
                                                                     options:0];
    [preds addObject:datePred];
  }
  if (![aStudyID isEqualToString:@""]) {
    NSExpression *studyIDKeyEx=[NSExpression expressionForKeyPath:@"studyID"];
    NSExpression *aStudyIDEx=[NSExpression expressionForConstantValue:aStudyID];
    NSPredicate *studyIDPred=[NSComparisonPredicate predicateWithLeftExpression:studyIDKeyEx
                                                                rightExpression:aStudyIDEx 
                                                                       modifier:NSDirectPredicateModifier
                                                                           type:NSLikePredicateOperatorType
                                                                        options:NSCaseInsensitivePredicateOption];
    [preds addObject:studyIDPred];
  }
  if (![aStudyName isEqualToString:@""]) {
    NSExpression *studyNameKeyEx=[NSExpression expressionForKeyPath:@"studyName"];
    NSExpression *aStudyNameEx=[NSExpression expressionForConstantValue:aStudyName];
    NSPredicate *studyNamePred=[NSComparisonPredicate predicateWithLeftExpression:studyNameKeyEx
                                                                rightExpression:aStudyNameEx 
                                                                       modifier:NSDirectPredicateModifier
                                                                           type:NSLikePredicateOperatorType
                                                                        options:NSCaseInsensitivePredicateOption];
    [preds addObject:studyNamePred];
  }
  p=[NSCompoundPredicate andPredicateWithSubpredicates:preds];  
  [preds release];
  return p;
}

Затем вы должны использовать его так:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
  NSDate *firstDate=[NSDate date];
  NSDate *secondDate=[firstDate dateByAddingTimeInterval:60*60*24];
  NSDate *thirdDate=[secondDate dateByAddingTimeInterval:60*60*24];

  // I use an array of dictionaries for convenience but it works
  //   for managed objects just as well. 
  NSDictionary *d1=[NSDictionary dictionaryWithObjectsAndKeys:
                   @"abc123", @"studyID", 
                   @"firstStudy", @"studyName", 
                   firstDate, @"studyDate", 
                   nil];
  NSDictionary *d2=[NSDictionary dictionaryWithObjectsAndKeys:
                    @"abc456", @"studyID", 
                    @"secondStudy", @"studyName", 
                    secondDate, @"studyDate", 
                    nil];
  NSDictionary *d3=[NSDictionary dictionaryWithObjectsAndKeys:
                    @"abc789", @"studyID", 
                    @"thirdStudy", @"studyName", 
                    thirdDate, @"studyDate", 
                    nil];
  NSArray *a=[NSArray arrayWithObjects:d1,d2,d3, nil];
  NSPredicate *p=[self predicateWithStudyID:@"" studyName:@"thirdStudy" date:thirdDate];
  NSLog(@"p = %@",p);  
  NSArray *filtered=[a filteredArrayUsingPredicate:p];
  NSLog(@"%@",filtered);
  return YES;                           
}

См. Руководство по программированию предикатов: создание предикатов непосредственно в коде

Обновлять:

@DaveDelong ниже указывает, что в первом блоке кода выше мое создание подпредикатов слишком сложно. В этом случае нет необходимости (кроме скорости выполнения) генерировать подпредикаты из выражений. Вместо этого просто создайте подпредикаты как обычно, а затем соедините их следующим образом:

-(NSPredicate *) predicateWithStudyID:(NSString *) aStudyID studyName:(NSString *) aStudyName date:(NSDate *) aDate{
  NSPredicate *p;
  NSMutableArray *preds=[[NSMutableArray alloc] initWithCapacity:1];
  if (aDate != nil) {
    NSPredicate *datePred=[NSPredicate predicateWithFormat:@"studyDate==%@",aDate];    
    [preds addObject:datePred];
  }
  if (![aStudyID isEqualToString:@""]) {
    NSPredicate *studyIDPred=[NSPredicate predicateWithFormat:@"studyID Like %@",aStudyID]; 
    [preds addObject:studyIDPred];
  }
  if (![aStudyName isEqualToString:@""]) {
    NSPredicate *studyNamePred=[NSPredicate predicateWithFormat:@"studyName Like %@",aStudyName]; 
    [preds addObject:studyNamePred];
  }
  p=[NSCompoundPredicate andPredicateWithSubpredicates:preds];  
  [preds release];
  return p;
}
person TechZen    schedule 26.03.2011
comment
Хотя создание объектов NSExpression, вероятно, быстрее во время выполнения, для работы требуется довольно много кода. Я рекомендую использовать либо шаблон предиката (@"keyPath = $value"), а затем подставлять нужные значения, либо просто использовать predicateWithFormat: для удобочитаемости. - person Dave DeLong; 27.03.2011
comment
Я пропустил создание предиката обычным способом, потому что автор ОП запросил по существу 6 различных возможных предикатов. Я не знаю простого способа построить предикат с переменным числом операторов И. Мои попытки сделать это с predicateWithFormat не увенчались успехом. - person TechZen; 27.03.2011
comment
правильно; Я имел в виду, что внутри каждого из операторов if() вы могли бы создать подпредикаты с обычным predicateWithFormat:, агрегировать их в массив (как вы делаете), а затем объединить их в NSCompoundPredicate (как вы делаете). - person Dave DeLong; 27.03.2011
comment
О, я вижу, да, это сработает. Думаю, я просто привык использовать NSCompoundPredicate с выражениями. - person TechZen; 27.03.2011

Вдохновленный этим вопросом, я создал свою собственную обобщенную выборку для моего диспетчера соединений для простоты повторного использования. Используя словарь, я могу отправлять пары ключ/значение, чтобы динамически генерировать соответствующий предикат и возвращать результаты, не беспокоясь об остальном каждый раз. Я надеюсь, что это помогает...

- (NSArray *)fetchObjects:(NSString *)entityName
                Parameters:(NSMutableDictionary *)parameters
{
    NSMutableArray *preds = [[NSMutableArray alloc] init];

    NSPredicate *pred;
    NSEnumerator *enumerator = [parameters keyEnumerator];

    for(NSString *aKey in enumerator)
    {
        pred=[NSPredicate predicateWithFormat:@"%K = %@", aKey, [parameters objectForKey:aKey]];
        [preds addObject:pred];

        [Log LogTrace:[NSString stringWithFormat:@"Query - Entity: %@ Predicate: %@ = %@", entityName, aKey, [parameters objectForKey:aKey]]];
    }

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity =
    [NSEntityDescription entityForName:entityName
                inManagedObjectContext:_managedObjectContext];
    [request setEntity:entity];


    NSPredicate *requestPreditace = [NSCompoundPredicate andPredicateWithSubpredicates:preds];
    [request setPredicate:requestPreditace];

    NSError *error;
    NSArray *array = [_managedObjectContext executeFetchRequest:request error:&error];

    [Log LogTrace:[NSString stringWithFormat:@"Results count: %d", [array count]]];

    if (error)
        [Log LogError:[NSString stringWithFormat:@"*** Core Data fetch ERROR: %@", [error localizedDescription]]];

    return array;
}
person JDCartee    schedule 10.12.2012