开发者

Dynamically Creating a Predicate with a Varying Number of Attributes and Values

I have an entity with three attributes: studyID:string, studyName:string and studyDate:date.

I need to create a predicate for searches but the user has the option to enter any possible combination of all three attributes e.g. one search is on studyID and studyDate but not studyName while the next search is just on studyID.

How to I create a predicate dynamically depending on what combination of attributes the user decides 开发者_运维技巧to search on?


You need to create a compound predicate whose final form depends on which attributes the user searches on.

The cleanest solution is to build the predicate in code using NSExpression, NSComparisonPredicate and NSCompoundPredicate. That will let you customize the predicate for each search.

Something like this:

-(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;
}

Then you would use it like so:

- (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;                           
}

See the Predicate Programming Guide: Creating Predicate Directly in Code

Update:

@DaveDelong below points out that in the first block of code above, my creation of the subpredicates is overly complex. There is no need in this case (other than execution speed) to generate the subpredicates from expressions. Instead just build the subpredicates as normal and then compound them like so:

-(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;
}


Using inspiration from this question I created my own generalized fetch for my connection manager for ease of reuse. Using a dictionary I'm able to send in key/value pairs to dynamically generate the appropriate predicate and return results without worrying about the rest each time. I hope it helps...

- (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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜