1
votes

I need to create a predicate to get object from core data, My entity "Form" looks like that:

@interface EEForm (CoreDataProperties)
@property (nullable, nonatomic, retain) NSDate *date;
@property (nullable, nonatomic, retain) NSString *descriptor;
@property (nullable, nonatomic, retain) NSString *location;
@property (nullable, nonatomic, retain) NSMutableSet<Participants *> *participants;

entity "Participants" look like that:

@interface Participants (CoreDataProperties)

@property (nonatomic, assign) NSInteger indexRow;
@property (nullable, nonatomic, retain) NSString *participant;
@property (nullable, nonatomic, retain) EEForm *form;

I want to get objects on the base of participants field, that contains all objects from a given array (this array consist of string objects and it changes dependently of what the user selects). Fetch request is performed by FetchResultController. I set the predicate in initialisation of it.

I did it in that way, but it includes all objects that contains at least one object from the given array.

- (NSFetchedResultsController *)fetchResultController {
    if(_fetchResultController != nil) {
        return _fetchResultController;
    }

    NSPredicate *lPredicate = [NSPredicate predicateWithFormat:@"ANY participants.participant IN %@", _selectedForPredicatel];
    NSFetchRequest *lRequest = [[NSFetchRequest alloc]init];
    [lRequest setPredicate:lPredicate];
    NSEntityDescription *lEntity = [NSEntityDescription entityForName:@"Form" inManagedObjectContext:self.managedObjectContext];
    [lRequest setEntity:lEntity];
    [lRequest setFetchBatchSize:10];
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:@"date" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
    [lRequest setSortDescriptors:sortDescriptors];
    NSFetchedResultsController *fetchResultController = [[NSFetchedResultsController alloc]initWithFetchRequest:lRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    fetchResultController.delegate = self;
    self.fetchResultController = fetchResultController;
    NSError *error = nil;
    if(![[self fetchResultController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    return fetchResultController;
}

Could you please advice how to set predicate properly to filter objects that contain all elements from the given array.

EDIT: array that is used for predicate looks like that:

   _selectedForPredicatel = [[NSMutableArray alloc]initWithObjects:@"Child",@"Adult", @"Group of people", @"Family", @"Teenager", nil];

every time user selects parameters by which filter 'Form' objects, this array is updating, so I need fetch request to be done according to it. So if 'Form' has included participants: @"Child",@"Adult", but in an array _selectedForPredicatel there are objects: @"Adult", @"Group of people", in this case this 'Form' shouldn't be fetched. Should be fetched only that 'Form'that includes both elements of _selectedForPredicatel array. Thanks a lot for any advice.

2
Can there be more than one Participant with the same value for the participant attribute?pbasdf
@pbasdf Yes, there could be a lot of participants with the same valueMelany

2 Answers

1
votes

You want to include the forms if, for each of the user's selections, there is at least one Participant that has a participant attribute that matches.

If the participant names were unique (at least for each Form), you could count the matches and compare to the count of the user's selections. But if not, you need to create a predicate test each of the user's selections separately, and then combine the results into one big predicate:

NSPredicate *template = [NSPredicate predicateWithFormat:@"(SUBQUERY(participants, $p, $p.participant == $SELECTED).@count > 0)"];
NSMutableArray *predicateArray = [NSMutableArray array];
for (NSString *testString in _selectedForPredicatel) {
    NSDictionary *subsVars = @{@"SELECTED" : testString};
    NSPredicate *testPredicate = [template predicateWithSubstitutionVariables:subsVars];
    [predicateArray addObject:testPredicate];
}
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicateArray];
NSLog(@"finalPredicate is %@", finalPredicate);
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"ListEntity"];
fetch.predicate = finalPredicate;

Here template is a dummy predicate using $SELECTED as a placeholder for the user's selection. The for loop uses this to create a predicate for each of the user's selections and adds it to a mutable array. The final predicate is built by compounding the array of predicates using AND clauses.

It's pretty messy (the final predicate is bad enough, the underlying SQL is horrible) so performance might be dismal.

1
votes

Instead of ANY you can use ALL :

 NSPredicate *lPredicate = [NSPredicate predicateWithFormat:@"ALL participants.participant IN %@", _sortByParticipants];

This will check if all objects are in your collection.