Core-Data NSPredicate with multiple to-many conditions - ios

Helo guys,
I have the following core-data database:
(Unfortunately I cannot embed the image, because of the spam prevention!)
Data base model image: http://snapplr.com/eqx9
I am trying to create a fetchRequest, which fetches all FC objects whose fcCollection.fcSets.trainingSet property is an object in my code BUT are not in trainingSet.memorizationSession.mistakeFCs. I want to use it in NSFetchedResultsController.
I have tried so many different things and they all didnt work (Unfortunately I didnt save what I tried, so I cannot post).
But I have tried SUBQUERIES and so on. My last approach is the following:
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName: kFCMistakeEntityName
inManagedObjectContext: self.managedObjectContext];
[fetchRequest setEntity: entity];
NSPredicate *subPredicate = [NSPredicate predicateWithFormat:
#"memorizationSession.trainingSet == %#", _trainingSet];
[fetchRequest setPredicate: subPredicate];
NSArray *mistakeFCs = [self.managedObjectContext executeFetchRequest:fetchRequest error: nil];
predicate = [NSPredicate predicateWithFormat:
#"ANY fcCollection.fcSets.trainingSet == %# && NOT mistakeFC in %#", _trainingSet, mistakeFCs];
Unfortunately I am getting this error:
2011-05-18 22:26:55.643 testApp[9464:207] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (mistakeFC IN {FCMistake: empty FC; correctFC: 0, FCMistake: 55; correctFC: 9, FCMistake: empty FC; correctFC: 3, FCMistake: empty FC; correctFC: 9, FCMistake: empty FC; correctFC: 4})'
This must be possible somehow! Thanks in advance for helping!
Greetings, Kim

From the Predicate Programming Guide:
The Core Data SQL store supports only
one to-many operation per query;
therefore in any predicate sent to the
SQL store, there may be only one
operator (and one instance of that
operator) from ALL, ANY, and IN.
so if you are using the SQL datastore you can only represent one one-to-many relationship in your predicates. If you change to some other persistence store it seems to imply that this particular restriction is lifted.

Related

Core Data predicate : unimplemented SQL generation for predicate

Basically I got 3 entities in my data model : Brand, Model and Trim.
A brand has a one-to-many relationship with Model called "models". (one brand have multiple models but a model has only one brand)
A model has a many-to-many relationship with Trim called "trims". (a model can have multiple trims, and a trim can have multiple models)
Having an array of trims objects, I would like to get all the brands having a model which "contains" at least one trim contained inside this array.
So here is my predicate for the fetch request :
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Brand"];
[NSPredicate predicateWithFormat:#"ANY models.trims IN %#", arrayOfTrims];
And here is the error I'm getting :
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (ANY models.trims IN {<Trim: 0x8e60340> (entity: Trim; id: 0x8e62f10 <x-coredata://BCD28560-AED5-4409-9A35-1925001773E6/Trim/p8>
I'm kinda new to Core Data and I have no idea what I'm doing wrong.
Hope someone can help,
Thanks a lot.
"ANY" in a Core Data predicate works only for a single to-many relationship.
Since your query involves two to-many relationships, you have to use a SUBQUERY:
[NSPredicate predicateWithFormat:#"SUBQUERY(models, $m, ANY $m.trims IN %#).#count > 0",
arrayOfTrims];
This exception is also raised if one of the predicates uses a column (i.e. field) name that does not exist. Totally misleading error message...
When you use a predicate in a CoreData operation, the predicate gets translated into SQL. That translation is not possible for all NSPredicate operations, you've hit one that isn't. My suggestion would be something along the lines of:
NSMutableArray* predicates = [NSMutableArray new];
for(NSString* trim in arrayOfTrims)
{
[predicates addObject:[NSPredicate predicateWithFormat:#"%# IN models.trims", trim]];
}
NSPredicate* predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];
The keyword IN can be used but you cannot apply ANY at the same time as that does not make sense when you turn it into SQL.
The predicate you are most likely looking for is:
[NSPredicate predicateWithFormat:#"models.trims IN %#", arrayOfTrims];
But that isn't going to work in this case either because you are going across a relationship. So what you need to do is reverse the whole thing:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Model"];
[request setPredicate:[NSPredicate predicateWithFormat:#"trims in %#", arrayOfTrims]];
NSError *error = nil;
NSArray *modelArray = [moc executeFetchRequest:request error:&error];
if (!modelArray) {
NSLog(#"Error: %#\n%#", [error localizedDescription], [error userInfo]);
}
NSArray parentObjectArray = [modelArray valueForKey:#"${PARENT_RELATIONSHIP_NAME}"];
Basically you are fetching the child objects to satisfy your ANY and then using KVC to retrieve the parent objects that you care about.
In my case I got this error because I made a silly mistake and didn't have the argument in the right data type.
I was trying to do:
let firstPredicate = NSPredicate(format: "firstName BEGINSWITH[cd] %#", firstNameField.stringValue)
and forgot to put the ".stringValue."

Core Data fetching relationship objects

in my app i have two entities: Members and Lists. they both have a one-to-many relationships (member can have more than one list, list can have more than one member). now i want to fetch the lists belonging to a specific member. here is my code:
WSAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Lists" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"has_members contains[cd] %#", [self.currentMember valueForKey:#"name"]]];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle the error.
NSLog(#"NO LISTS AVAILABLE IN REFRESH");
}
self.currentMember is a managed object of the user himself.
Note: member has name, (NSSet*) member_of_list
list has list_name, has-members
Problem: when i run the code it's breaking at the fetchedObjects array. i suspect that there is something wrong with the NSPredicate but i don't know where and how to fix it. can any one point out the problem?
First, the relationship you describe between Member (Calling an entity in a plural form is confusing) and List is many-to-many.
Second, instead of using CoreData's inherent object graph capabilities, you went and "rolled your own" relationship between the entities (you should use your interface builder to model a CoreData relationship between the two entities).
See HERE how to do that.
after you model your data, your predicate should look something like:
//Not tested
NSPredicate* p = [NSPredicate predicateWithFormat:#"ANY members = %#",self.currentMember];
DO NOT pass a formatted string to create the predicate, use NSPredicate formatting to substitute parameters or you will not be able to accomplish your goal (in most cases).

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]]

Coredata fetching and grouping objects in sections

I am working with a pretty complex object model and am having a bit of trouble with breaking some of my fetches down into sections to display in a tableview.
I have the need to group Meeting managed objects into a few different "pockets" such as project, client and a few others. For several reasons I've decided to implement these as tags that can be associated with a Meeting entity.
So I have created a new Tag entity which has a type and a value and established the relationships between the two:
Meeting <<-->> Tag
If I want to associate a Meeting with a project, I create a Tag with name 'project' and value 'Project Name', then add it to the Meeting entity via the relationship.
I initially thought about using a NSFetchedResultsController but I am getting all sorts of issues all of which I don't really understand.
For instance, this fetch (I'm omitting the unnecessary bits):
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Meeting entityName] inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"tags.name contains[] 'client'"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
[fetch setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:moc
sectionNameKeyPath:#"self.tags.value"
cacheName:nil];
In this particular case the fetch does work but somehow I am getting unexpected results where not only Tags with value client are presented but also ones with value project???
If I change my predicate to tags.name == 'project' I get an exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
I'm probably missing something basic here and admittedly I don't have a lot of experience with predicates but Apple's documentation on the subject leaves a lot to be desired.
As a side question and something that I don't understand either is why do I have to add self to my sectionNameKeyPath in self.tags.value? What does it do?? In this case if I don't add it I get an exception thrown as well:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid to many relationship in setPropertiesToFetch: (tags.value)
Finally, what is the alternative to using fetched results controller in this case? Will it be a bunch fetch requests where I first get each instance of Tag where name == 'project' and iterate through the array to pull the Meeting objects associated with it? It seems highly inefficient but all I can think of at the moment so if you have any other ideas I am very interested in hearing them.
Many thanks in advance for your time,
Rog
The issue is that Meeting has-many tags, so you're going to need to use aggregate operations:
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Meeting entityName] inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.name contains[cd] 'client'"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
[fetch setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:moc
sectionNameKeyPath:#"clientName"
cacheName:nil];
i.e. Give me a list of all Meeting objects where ANY of the tags are of type client, and group them by clientName. For the clientName key path to work, you'll need to implement a transient property:
- (NSString *)clientName {
[self willAccessValueForKey:#"clientName"];
// Set clientName to the value of the first tag with name 'client'
NSString* clientName = #"...";
[self didAccessValueForKey:#"clientName"];
return clientName;
}
If a number of your NSManagedObject subclasses need the clientName property, you can implement it in a common abstract NSManagedObject subclass and make your concrete subclasses inherit from that.
"Apple's documentation on [NSPredicate] leaves a lot to be desired" - totally agreed!
tags.name is not valid because tags is a collection, there's no object, there are (let's say) three of them
i think you want something like "tags contains %#", projectTag but i'm not sure of the syntax. might be "%# IN %#", projectTag, thing.tags

Core Data: NSPredicate containing to-many relationship yields no results when using SQLITE store type

I have been using Core Data with In-Memory store for my project until recently. Now I am trying to switch to SQLite store and I'm facing the following issue:
When Trying to fetch objects from the store using a predicate and sortDescriptor, my fetchedResultsController returns 0 objects. The exact same code works fine when the store type is in-memory.
This is how I am making the fetch:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %K == %#", #"categories", category];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES selector:#selector(caseInsensitiveCompare:)];
self.fetchedResultsController = [self createFetchedResultsControllerWithEntityName : #"Provider" sectionName : #"group" sortDescriptor : sortDescriptor predicate : predicate cache : nil];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self.tableView reloadData];
and this is where I create the fetch request:
- (NSFetchedResultsController *)createFetchedResultsControllerWithEntityName : (NSString*) entityName sectionName : (NSString*) sectionName sortDescriptor : (NSSortDescriptor*) sortDescriptor predicate : (NSPredicate*) predicate cache : (NSString*) cacheName{
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//set predicate
if (predicate)
[fetchRequest setPredicate:predicate];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:sectionName
cacheName:cacheName];
aFetchedResultsController.delegate = self;
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
return [aFetchedResultsController autorelease];
}
Again, this very same code fetches many objects when using in-memory store, but an empty fetchedObjects array when using SQLite.
I've read Apple's documentation with regard to the special considerations to make when using predicates and sortDescritors with SQLite store. I am using a single (many-)to-many relationship (categories) in the predicate, so this should be fine?
There is no error returned from the fetch. Simply no objects (fetchedResultsController.fetchedObjects is empty).
Any guidance would be greatly appreciated.
Thanks
I've figured out this issue eventually.
My query returned no results there were indeed no results. Tracing this back to where I was creating the objects and populating the realtionships with values I noticed that I was doing it wrong. I tried to do:
[provider.categories addObject : newCategory];
Which did not really add newCategory to the categories relationship.
After having realized that Core Data generates the accessors for to-many relationships automatically, I used the following to add newCategory:
[provider addCategoriesObject : newCategory];
This properly added newCategory to the relationship.
In addition to that I declared addCategoriesObject in Provider.h to suppress the compiler warning. This resolved the issue for me.
The only remaining caveat is that the compiler does not find the implementation of addCategoriesObject: and warns about "incomplete implementation". I haven't figured out a way to suppress this warning too.
I know SQLite stores have problems with "ANY" fetches in some cases (I don't remember the exact ones). Maybe try modifying your search predicate to see if you get different results, e.g.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"categories LIKE %#", category];

Resources