CoreData generic findManyByKey

Wed, 09. Dec 2009

Categories: en development Tags: Cocoa CoreData iPhone NSEntityDescription NSManagedObject NSManagedObjectContext NSPredicate Objective C

The base for many of my SELECT-ish queries when querying by exact match is one generic method I created in some category methods on NSManagedObjectContext:

-(NSArray*)entityName:(NSString*)entityName findManyByRelation:(NSDictionary*)dict
{
//  TODO handle dict nil and emptyness
    NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:dict.count];
    for(NSString *key in dict)
    {
        NSExpression *left = [NSExpression expressionForKeyPath:key];
        NSExpression *right = [NSExpression expressionForConstantValue:[dict objectForKey:key]];
        NSComparisonPredicate *cp = [[NSComparisonPredicate alloc] initWithLeftExpression:left
            rightExpression:right modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
        [arr addObject:cp];
        [cp release];
    }
    NSPredicate *pred = nil;
    if(arr.count == 1)
        pred = [[arr objectAtIndex:0] retain];    // why do I have to retain here?
    else
        pred = [[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:arr];

    NSFetchRequest *fr = [[NSFetchRequest alloc] init];
    fr.entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self];
    fr.predicate = pred;
    [arr release];
    [pred release];
//  NSLog(@"predicate effective: %@", fr.predicate);

    NSError *err = nil;
    NSArray *ps = [self executeFetchRequest:fr error:&err];
    if(err != nil)
        [NSException raise:@"DB Error" format:@"Fetch problem %@", fr.predicate.predicateFormat];
    [fr release];
    return ps;
}

For example it serves under the hood of it’s sibling helpers

-(NSManagedObject*)entityName:(NSString*)entityName findByPrimaryKey:(NSDictionary*)dict
{
    NSArray *ps = [self entityName:entityName findManyByRelation:dict];
    if(ps == nil || ps.count == 0)
        return nil;
    if (ps.count == 1)
        return [ps objectAtIndex:0];
    [NSException raise:@"DB Error" format:@"Multiple hits for %@", dict];
    return (NSManagedObject*)1/0;
}

and

-(NSManagedObject*)entityName:(NSString*)entityName selectOrInsertWithKey:(NSDictionary*)dict
{
    NSManagedObject *o = [self entityName:entityName findByPrimaryKey:dict];
    if(o == nil)
    {
        NSEntityDescription *ed = [NSEntityDescription entityForName:entityName inManagedObjectContext:self];
        o = [[NSManagedObject alloc] initWithEntity:ed insertIntoManagedObjectContext:self];
        for(NSString *key in dict)
            [o setValue:[dict objectForKey:key] forKey:key];
        [o autorelease];
    }
    return o;
}

Those methods again are called by custom convenience wrappers in the model classes loosely following the ActiveRecord Pattern.