Core Data - filtering a To-Many Relationship using Predicates

Steven Green picture Steven Green · May 27, 2013 · Viewed 16.4k times · Source

I have the following two entities in my Core Data Model:

Manufacture {name, ...other attributes}
Product {name, .... other attributes}

I have setup a One to Many Relationship:

Manufacturer.manufactures <------>> Product.manufacturedBy

I am trying to build a predicate to return all Products belonging to Manufacturers that match a search string. E.g. if there are two Manufacturers, "King Nut", and "Queen Nut" then a search on "Nut" should return all products made by both King Nut and Queen Nut.

My predicate works perfectly when my filter is on the Product entity, however I cannot get any predicate to work when filtering on the Manufacturer entity. The result set is empty.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Product" inManagedObjectContext:[GBKDB context]];
searchValue = @"nut";
NSString *wildcardString = [NSString stringWithFormat:@"*%@*", searchValue];

I have tried the following:

    predicate = [NSPredicate predicateWithFormat:@"manufacturedBy.name CONTAINS[cd] %@",searchValue];
    predicate = [NSPredicate predicateWithFormat:@"manufacturedBy.name like %@",wildcardString];
    predicate = [NSPredicate predicateWithFormat:@"manufacturedBy.name matches %@",wildcardString];
    predicate = [NSPredicate predicateWithFormat:@"ALL manufacturedBy.name like %@",wildcardString];
    predicate = [NSPredicate predicateWithFormat:@"ALL manufacturedBy.name like[cd] %@",@wildcardString];

Answer

Dan Shelly picture Dan Shelly · May 27, 2013

To get Products manufactured by a manufacturer with name containing "nut", your request should look like:

NSString* searchVal = @"nut";
NSFetchRequest* r = [[NSFetchRequest alloc] initWithEntityName:@"Product"];
[r setPredicate:[NSPredicate predicateWithFormat:@"manufacturedBy.name CONTAINS[cd] %@",searchVal]];

To get Manufacturers with product names containing "nut" your request should look like:

NSString* searchVal = @"nut";
NSFetchRequest* r = [[NSFetchRequest alloc] initWithEntityName:@"Manufacture"];
[r setPredicate:[NSPredicate predicateWithFormat:@"ANY manufactures.name CONTAINS[cd] %@",searchVal]];

If your result set is empty, it might be due to the fact that no objects answer the predicate (contain the substring "nut").
Try adding some fake entities with known names and testing.

Edit: This is the code you could use for testing:

typedef void(^config_block_t)(id);

- (void) synthesizeObjectsOfEntity:(NSString*)entity
                           context:(NSManagedObjectContext*)context
                             count:(NSUInteger)count
                       configBlock:(config_block_t)configBlock
{
    for (;count;--count) {
        NSManagedObject* object = [NSEntityDescription insertNewObjectForEntityForName:entity
                                                                inManagedObjectContext:context];
        configBlock(object);
    }
}

- (void) synthesizeProductsAndManufacturersInContext:(NSManagedObjectContext*)context
{
    NSMutableArray* manufacturers = [NSMutableArray new];
    [self synthesizeObjectsOfEntity:@"Manufactur"
                            context:context
                              count:10
                        configBlock:^(Manufactur* m) {
                            m.name = [NSString stringWithFormat:@"m-%u%u%u",arc4random()%10,arc4random()%10,arc4random()%10];
                            [manufacturers addObject:m];
                        }];
    [self synthesizeObjectsOfEntity:@"Product"
                            context:context
                              count:100
                        configBlock:^(Product* p) {
                            p.name = [NSString stringWithFormat:@"p-%u%u%u",arc4random()%10,arc4random()%10,arc4random()%10];
                            p.manufacturedBy = manufacturers[arc4random() % [manufacturers count]];
                        }];
    [context save:NULL];
    [context reset];
    NSString* searchVal = @"3";
    NSFetchRequest* r = [[NSFetchRequest alloc] initWithEntityName:@"Product"];
    [r setPredicate:[NSPredicate predicateWithFormat:@"manufacturedBy.name CONTAINS[cd] %@",searchVal]];
    NSArray* match = [context executeFetchRequest:r error:NULL];
    NSLog(@"matched: %u",[match count]);
}