NSFetchedResultsController with NSPredicate - ios

I am using a NSFetchedResultsController to handle the data going in and out of my UITableView. It all works fine until I put a NSPredicate on it. I need to get all Thread objects that have a relationship with at least one message that is not expired or does not have a expiration_date set yet. So I use the following predicate.
NSPredicate *threadPredicate = [NSPredicate predicateWithFormat:#"ANY messages.expiration_date == null OR ANY messages.expiration_date > %#", [NSDate date]];
Problem is that this causes the NSFetchedResultsController to act very strange. If a thread gains a new message in its messages relationship it deletes the row. It also does not insert new ones.
I am 100% sure it is the NSPredicate causing these behaviors. Without it, everything works fine I just don't have some of the data I do not want filtered out.
Below is a picture of the talked about section of my data model. I will also include the code for my NSFetchedResultsController.
/**
* Returns a NSFetchedResultsController for the unified inbox
*
* #return Controller for fetched results
*/
- (NSFetchedResultsController *)resultsControllerForUnifiedInbox
{
NSFetchRequest *threadsRequest = [NSFetchRequest fetchRequestWithEntityName:#"Thread"];
NSEntityDescription *threadModel = [NSEntityDescription entityForName:#"Thread" inManagedObjectContext:_mainManagedObjectContext];
[threadsRequest setEntity:threadModel];
NSPredicate *threadPredicate = [NSPredicate predicateWithFormat:#"ANY messages.expiration_date == null OR ANY messages.expiration_date > %#", [NSDate date]];
[threadsRequest setPredicate:threadPredicate];
NSSortDescriptor *threadSort = [NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO];
[threadsRequest setSortDescriptors:[NSArray arrayWithObject:threadSort]];
[threadsRequest setFetchBatchSize:LiftFetchControllerAtrributeBatchSize];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:threadsRequest
managedObjectContext:_mainManagedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
return fetchedResultsController;
}

I cannot explain the strange effects of the predicate that you observed, but I assume
that your predicate is not doing what you expect.
[NSPredicate predicateWithFormat:#"ANY messages.expiration_date == null OR ANY messages.expiration_date > %#", [NSDate date]];
finds all threads that have a message with expiration_date not set OR have a possible different message that is not expired. What you (probably) need instead is
[NSPredicate predicateWithFormat:#"SUBQUERY(messages, $m, $m.expiration_date == null OR $m.expiration_date > %#).#count > 0", [NSDate date]];

Related

NSPredicate multiple ANY query with CoreData

I'm trying to find all instances of an object that contain a reference to a combination of separate objects in my object graph.
recommendation
may contain one or more of the following three objects:
damageType
areaDamaged
validVehicles
This structure is built from an import of an existing system's file format and I am unable to restructure the object graph.
I'm using an NSPredicate to find all recommendation objects that have a damageType matching a selected damage as follows:
NSFetchRequest *fetchRequestDamages = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([Recommendation class])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY damageType == %#", _currentRecordedDamage.damageType];
But want the filter to return all Recommendations that have matches for a specific damageType, areaDamaged and validVehicle
I've tried
NSMutableArray *predicates = [[NSMutableArray alloc] initWithCapacity:2];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY damageType == %#", _currentRecordedDamage.damageType];
[predicates addObject:predicate];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"ANY areaDamaged == %#", _currentAreaDamaged];
[predicates addObject:predicate2];
NSPredicate *predicate3 = [NSPredicate predicateWithFormat:#"ANY validVehicles == %#", _currentVehicle];
[predicates addObject:predicate3];
fetchRequestDamages.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
fetchRequestDamages.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequestDamages managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error;
[self.fetchedResultsController performFetch:&error];
int resultsFound = self.fetchedResultsController.fetchedObjects.count;
but it seems this returns the set of all objects satisfying any of the predicates - I'd like the set of objects that match all three.
I'm looking into using SUBQUERY but can't quite make sense of how to create this query?
Just combine the three predicates with "AND" to find the objects that match all of them:
NSArray *predicates = ... // your array of predicates
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[fetchRequestDamages setPredicate:finalPredicate];
Why not to use AND in one query?
Something like:
damage = %# AND damagePoints = %# AND damageCost = %#
and:
damageType IN %#
Where %# in the last code example should be an array/set or something else.

predicate for fetching an entities' assosicate entities where associate entity's certain attribute has a certain string value

i have catalogues and categories entities in my coredata
they have a many-to-many relationship
i want to fetch specific categories where field categoryStatus="active" and involved in catalogue.categories
what is the proper predicate ?
or without a fetch > catalogue.categories where categoryStatus=#"active"
c# equivelant of aCatalogue.categories.Where(c=>c.categoryStatus == "active")
i try something like this but no luck
-(NSArray *) CategoryGetListWhereStatusActive4Catalogue:aCatalogue2DisplayCategories
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Category" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"([catalogues containsObject:%#) AND (categoryStatus == %#)" , aCatalogue2DisplayCategories, #"active"];
[fetchRequest setPredicate:predicate];
return [self performFetch:fetchRequest withContextObject:context];
}
Something like this?
[NSPredicate predicateWithFormat:#"(ANY catalogues == %#) AND (categoryStatus == %#)",
aCatalogue, #"active"]
I think this should work:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(SUBQUERY(catalogues, $catalogues, $catalogues == %#).#count>0) AND (categoryStatus == %#)" , aCatalogue2DisplayCategories, #"active"];
or
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(ANY catalogues = %#) AND (categoryStatus == %#)" , aCatalogue2DisplayCategories, #"active"];

CoreData NSPredicate with relationship

I have following CoreData objects Model
Now I am having issue in making a predicate with following conditions.
Fetch all those DBOpportunity WHERE
DBOpportunity.stateCode == 1
AND
DBOpportunity.invoiceDate >= GIVEN_DATE
AND
DBOpportunityLines.crmAccept == 1 OR DBOpportunityLines.crmAccept == 3
I have tried lots of examples and programming guide by the apple but can't able to achieve this.
opportunitylines is a to-many relationship, so there are multiple DBOpportunityLines objects for one DBOpportunity object. Assuming that the last condition
DBOpportunityLines.crmAccept == 1 OR DBOpportunityLines.crmAccept == 3
should hold for any of the related objects, you need a SUBQUERY:
NSDate *givenDate = ...;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"stateCode == 1 AND invoiceDate >= %# "
"AND SUBQUERY(opportunitylines, $x, $x.crmAccept == 1 OR $x.crmAccept == 3).#count > 0",
givenDate];
Remark: Unfortunately, the usage of SUBQUERY in predicates is poorly documented. There is one example in the NSExpression class reference. See also Quick Explanation of SUBQUERY in NSPredicate Expression.
The structure of your predicate is A && B && (C || D)
Setup your predicates
NSPredicate *aPredicate = [NSPredicate predicateWithFormat:#"stateCode == %d", value];
NSPredicate *bPredicate = [NSPredicate predicateWithFormat:#"invoiceDate >= %#", givenDate];
Similar do the cPredicate and dPredicate. Then first combine c and d with OR
NSArray *cdPredicateArray = #[cPredicate, dPredicate];
NSPredicate *cdPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:cdPredicateArray];
and then all of them with AND
NSArray *allPredicateArray = #[aPredicate, bPredicate, cdPredicate];
NSPredicate *allPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:allPredicateArray];
If I misunderstood your question and your structure is A && B && C || D Then you have to combine A, B and C first (with AND) and then combine that result with D (with OR).
You could also fetch your opportunityLines and then get the parent entities like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"opportunityLines" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSDate *yourDate = [NSDate date];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(crmAccept==1 OR crmaccept==3) AND opportunity.stateCode==1 AND opportunity.invoiceDate>=%#", yourDate];
[fetchRequest setPredicate:predicate];
NSError *error;
//So here you have your array of opportunitylines
NSArray *opportunityLines = [context executeFetchRequest:fetchRequest error:&error];
//And this is how you get your opportunity objects
NSArray *opportunities = [opportunityLines valueForKeyPath:#"#distinctUnionOfObjects.opportunity"];

Fetchrequest with NSPredicate not returning right values

I'm having a strange problem with Core Data.
The problem is that I'm looking for objects with a property less than X and it isn't returning all the values that matches.
There is no cache and I'm not using //[fetchRequest setFetchBatchSize:50];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"self.level <= [cd] %#", [self.filters objectForKey:#"level"]];
I add it to a MutableArray of predicates and later I execute
NSPredicate *myPredicate = [NSCompoundPredicate andPredicateWithSubpredicates: subPredicates];
This one it's in a function that returns myPredicate called preparePredicates. For the moment there aren't more predicates, only one.
It's NSLog returns: level <=[cd] "30".
I have tried it also with intValue of the string and %i
The main function:
NSError* error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myEntity"
inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSPredicate* predicate = [self preparePredicates];
[fetchRequest setPredicate:predicate];
//[fetchRequest setFetchBatchSize:50];
NSArray *fetchedObjects = [self.context executeFetchRequest:fetchRequest error:&error];
NSLog (#"Error: %#", error);
NSLog(#"los fetchedobjects: %i",[fetchedObjects count]);
return fetchedObjects;
It doesn't return any error.
If I look at the results there isn't one having a level of 6, but there are others that matches (all are less than the specified). I know there is one with level 6.
If I open the .sqlite in SQLite Database Browser, I can see it there.
If I do with this program a
SELECT * FROM zmyentity WHERE zlevel <30;
it doesn't appear, same as in Core Data do. But if I do a
SELECT * FROM zmyentity WHERE zlevel == 6;
it appears.
I really don't know what is happening. Maybe I'm making a rookie mistake, so please point me in the right direction.
Thank you very much in advance.
Probably you have stored the level as a String attribute, because
"30" < "6"
when comparing strings. Choosing a numeric attribute type (Integer 16/32/64) should solve the problem. The corresponding level property in the myEntity class has then the NSNumber type.
You should also omit the [cd] modifier in the predicate, because that enforces a string comparison.
You predicate could then look like this:
[NSPredicate predicateWithFormat:#"self.level <= %d", [[self.filters objectForKey:#"level"] intValue]]

NSPredicate, get results with a subset of one-to-many relationship

I'mm working around with Core Data and NSFetchedResultsController.
My Data Model looks like this:
Product with one-to-many relationship called dataLines.
The dataLine entity has a property name theWeek.
I want to fetch all Product where dataLines.theWeek == someValue. This is easily done with a subquery. But this returns all dataLines. Is it possible to create a NSPredicate that returns the Product and a subset if dataLines only with the dataLines == someValue?
What you want to achieve could be reached in two ways:
using a SUBQUERY
[NSPredicate predicateWithFormat:#"SUBQUERY(dataLines, $x, $x.theWeek == %#).#count > 0)", [NSNumber numberWithInt:18]];
or the ANY modifier
[NSPredicate predicateWithFormat:#"ANY dataLines.theWeek == %#", [NSNumber numberWithInt:18]];
You can do also the following if you need to check against multiple values:
[NSPredicate predicateWithFormat:#"SUBQUERY(dataLines, $x, $x.theWeek == %# or $x.theWeek == %#).#count > 0)", [NSNumber numberWithInt:18], [NSNumber numberWithInt:19]];
The same can be applied to ANY modifier. ANY ... OR ANY ....
Maybe if you share some code we could help you.
P.S. I suppose you don't use scalar values and theWeek is a number.
Hope it helps.
You should fetch the dataLine property instead.
Assuming your Product and dataLine entity connected by relationship someRelation then you can try this code;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityWithName:#"dataLine" inManagedObjectContext:self.managedObjectContext]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"dataLines.week == %#",theWeek]];
NSMutableArray *tmpProduct [[NSMutableArray init] alloc];
NSMutableArray *tmpArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (dataLine *theDataLine in tmpArray);
NSLog(#"%#",theDataLine.someRelation.name);
tmpProduct = theDataLine.someRelation.name;
then you can just call tmpProduct to call or display your product in table view
Create a fetch request for the 'Product' entity:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: [NSEntityDescription entityForName:#"Product" ...]]
then create a predicate using the properties/attributes of Product with 'ANY':
[fetchRequest setPredicate:
[NSPredicate predicateWithFormat:#"ANY dataLines.theWeek == %#", <whatever week>]];
then execute the fetch to get an array of Product with at least one <whatever week>.
Generally see 'Fetching Managed Objects', NSPredicate and related documentation.

Resources