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;
}
精彩评论