views:

1724

answers:

2

I have a Core Data model in which a Task entity includes an optional to-many relationship ExcludedDays to the ExcludedDay entity. One of the properties of ExcludedDay is day, which is an NSDate object. The ExcludedDay entity has an inverse mandatory to-one relationship to the Task entity.

In order to fetch the tasks for a specified day, I need to make sure that the specified day does not appear as the day property of any ExludedDay entity.

I started trying

NSPredicate *dayIsNotExcludedPredicate = [NSPredicate predicateWithFormat: @"ALL excludedDays.day != %@", today];

However, despite what the documentation says, ALL does not work and the application throws an exception: Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Unsupported predicate.

After posting the same question in this forum, I was able to devise the following predicate with the help of various people:

NSPredicate * dayIsNotExcludedPredicate = [NSPredicate predicateWithFormat: @"excludedDays.@count == 0 || (excludedDays.@count > 0 && NONE excludedDays.day == %@))", today];

While this worked at first, I have just discovered that this only works when the ExcludedDay entity contains ONLY one day. As soon as the ExcludedDay entity contains more than one day for the same task, this predicate stops working. As a result, a task is selected for a day even though the day appears as a day in the ExcludedDay entity, which is of course wrong. The problem is not tied to the property day being a NSDate object: replacing day with the corresponding NSString or equivalently with an integer, I still face the same issue and incorrect behaviour.

What is the correct way to implement the predicate in this case? May this be a bug related to the ANY aggregate operator when using core data? Thank you in advance, this is now driving me crazy.

A: 

Quite curious how to solve this I setup a little project and created the context you are using.

NSDate *today = [NSDate date];
NSMutableArray *objects = [NSMutableArray array];

{
 NSArray *day = [NSArray arrayWithObjects:today, [today dateByAddingTimeInterval:20.0f], nil];
 NSDictionary *excludedDay = [NSDictionary dictionaryWithObject:day forKey:@"day"];
 NSDictionary *object = [NSDictionary dictionaryWithObject:excludedDay forKey:@"excludedDay"];

 [objects addObject:object];
}

{
 NSArray *day = [NSArray arrayWithObjects:[today dateByAddingTimeInterval:20.0f], nil];
 NSDictionary *excludedDay = [NSDictionary dictionaryWithObject:day forKey:@"day"];
 NSDictionary *object = [NSDictionary dictionaryWithObject:excludedDay forKey:@"excludedDay"];

 [objects addObject:object];
}


NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE excludedDay.day == %@", today];
NSArray *filtered = [objects filteredArrayUsingPredicate:predicate];

NSLog(@"%@", filtered);

This gives the object when:

  1. The day array is empty
  2. The day array does not contain the 'today' date

This does not give the object when:

  1. The day array contains the 'today'
    • It doesn't matter how many objects in the day array are
JoostK
Unfortunately, this is a completely different setting: my context is based on Core Data and iPhone SDK 3.0 or 3.1. It seems that the Core Data framework is not handling correctly predicates when following a to-many relationship. I have already filed a bug and the Apple engineers are working on it, but still no answer from them.
unforgiven
Then it is indeed a Core Data relayed issue, since using the same predicate but without Core Data it is working properly (as seen in my attempt to mimic the same (Foumdation-related) context)
JoostK
+2  A: 

It turns out this is yet another problem with missing and/or inconsistent documentation.

The correct predicate in this case is the following one:

[NSPredicate predicateWithFormat:@"(excludedOccurrences.@count == 0) OR (0 == SUBQUERY(excludedOccurrences, $sub, $sub.day == %@).@count)", today]

In the predicate, a subquery is used to test if the number of related excludedOccurrences with a date matching your test date is equal to zero. However, the documentation is misleading. Here is what the Predicate Programming Guide says regarding the use of predicates in conjunction with to-many relationships.

Using Predicates with Relationships

If you use a to-many relationship, the construction of a predicate is slightly different. If you want to fetch Departments in which at least one of the employees has the first name "Matthew," for instance, you use an ANY operator as shown in the following example:

NSPredicate *predicate = [NSPredicate predicateWithFormat:
    @"ANY employees.firstName like 'Matthew'"];

If you want to find Departments in which all the employees are paid more than a certain amount, you use an ALL operator as shown in the following example:

float salary = ... ;
NSPredicate *predicate = [NSPredicate predicateWithFormat:
    @"ALL employees.salary > %f", salary];
unforgiven
Maybe you know how to write the expression with SUBQUERY with NSExpresion and NSComparisonPredicate classes?With your answer, I decided the my question:http://stackoverflow.com/questions/2006927/whats-better-way-to-build-nspredicate-with-to-many-deep-relationshipsThanks
Victor