NSPredicate with Core Data - objective-c

I've look through apple documentation and SO and I cannot find this simple code.
I will accept any down votes I get for such a question, but what is the predicate string that will return a given entity where that entity's property is equivalent to a given string.
I have company and product entities. At the moment, they are simple. Company has name and some other attributes. Product has manufacturer and some other attributes.
I want to do a fetch for all products where the products manufacturer attribute is equivalent to the name of the company that was selected in the company's view controller.
Here is the method in my data model that handles updating the products when a company is selected. It uses the title of the product view controller because that property is assigned the company name in didSelectItemAtIndexPath.
-(void)updateProducts {
NSFetchRequest *reqProd = [[NSFetchRequest alloc] init];
NSEntityDescription *entProd = [NSEntityDescription entityForName:#"Product" inManagedObjectContext:self.context];
[reqProd setEntity:entProd];
NSString *companyToFilter = self.productsViewController.title;
reqProd.predicate = [NSPredicate predicateWithFormat:#"manufacturer like %#", companyToFilter];
NSError *errorProd = nil;
self.productList = [self.context executeFetchRequest:reqProd error:&errorProd];
}
The problem is that this array always returns 0 elements. I know I have a product named iPhone who's manufacturer is apple. Therefore, it must be the predicate that is generating inaccurate SQL.
Again, I spent way too much time search for the answer to no avail. would somebody help me and explain the proper string for the predicate or direct me to the proper documentation?

Predicates are not exactly like SQL, but you can get it to output the SQL being used by adding -com.apple.CoreData.SQLDebug 1 in the startup options.
Also, the predicate format is documented. The short answers to your question is as John says: use CONTAINS instead of LIKE.

The semi-equivalent of the SQL LIKE operator in CoreData is BEGINSWITH for prefixes, ENDSWITH for suffixes, or CONTAINS for matches anywhere in the string.
[NSPredicate predicateWithFormat:#"manufacturer CONTAINS %#", companyToFilter]
You can also write CONTAINS[cd] to indicate "case and diacritic insensitive", or just CONTAINS[c] or CONTAINS[d].

Related

Objective-C - wrap attribute value with commas in NSPredicate CONTAINS

Here is the situation. I have some Core Data data and I want to get the data where the value of an attribute wrapped with two commas contains another string.
This is my current code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type CONTAINS %#", [NSString stringWithFormat:#",%#,",typeBar]];
Simply put, this is what I want to achieve:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#",type, CONTAINS %#", [NSString stringWithFormat:#",%#,",typeBar]];
With the commas around the 'type'. But obviously when I do it like this, it doesn't recognise the attribute anymore. I have tried ,'type', but that doesn't work either.
I have tried this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#",%K, CONTAINS %#", #"type", [NSString stringWithFormat:#",%#,",typeBar]];
But I get a: 'NSInvalidArgumentException', reason: 'Unable to parse the format string ",%K, CONTAINS %#"'
I hope the question was clear enough.
Thanks a lot!
You can't do that. What you're trying is attempting to change the key to be queried to something invalid, hence the parse error.
You can't really do what you want to if you just store a plain type. Presumably your type may be a string list of things?
So, you need to rethink your approach, there are a number of options depending on what your underlying problem is:
Store the text for type with commas at the beginning and end (as well as between items)
Don't use a string, use some other entity (if you have a list of items)
Predicate on the plain type, without the commas, and then filter the results in more detail once you have the items out of Core Data (where predicates are restrictive compared to 'full' code access to the strings)

Fetching data using Core Data

I have searched quite a lot on the internet but can't find what I'm looking for.
I have this model where it could be a lot of users. So I have an entity called User. The user has an NSSet of records. And I want to fetch records from given user. I'm trying to do it like this but it still returns records from all users.
NSManagedObjectContext *context = _backgroundContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Record"
inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date >= %#)",date];
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"User = %#",currentUser];
NSPredicate *predicates = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:predicate,predicate1, nil]];
[fetchRequest setPredicate:predicates];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
I know it shouldn't be hard, but I can't find what it is, and I'm hoping some of you could help. Thanks in advance!
EDIT:
As I said I have an entity User which has:
NSString name,
int age,
NSSet records, ...
Record has its own properties such as:
NSDate date,
NSString name,
NSString event,
...
I want to form a fetch request to get records just from specific user. And I don't know how to do it, because I'm getting all of the records from every user.
records has a To-Many relationship. I can get records like currentUser.records, but i can't get user using record.User.
I'm assuming your model looks roughly like this, i.e. the records relationship has an inverse relationship called user:
It's importantant that this relationship is an inverse relationship because otherwise Core Data will not automatically maintain it. You can then query all Records for the given User on/after the given date like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Record"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"user = %# AND (date >= %#)", currentUser, date];
NSError *error = nil;
NSArray *records = [_backgroundContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"records: %#", records);
I've rewritten your code to be more compact, but the important change is that this will create the query in just one go.
The error is here:
[fetchRequest setPredicate:predicates];
[fetchRequest setPredicate:predicate];
After setting the compound predicate, you overwrite it with the predicate for date alone. You probably want to delete the second line.
EDIT: The fetch request requires that you have defined a inverse relationship user from Record to User and use the exact name of this relationship in the predicate.
An alternative solution is to use the "forward" relationship from User to Record and filter the result:
NSSet *records = [currentUser.records filteredSetUsingPredicate:predicates];
or, if you prefer an array
NSArray *records = [[currentUser.records allObjects] filteredArrayUsingPredicate:predicates];
As Martin R said, you're replacing your predicate.
As he also mention, the user property should be lowercase, as I'm pretty sure that core-data enforces this. It should give you an error if you try creating a relationship with an upper case letter, so try
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"user = %#",currentUser];
instead. Obviously if your model doesn't define a property for user then that won't work either and you'll need to change it to whatever your model actually has, but that should work otherwise.
Edit:
Try adding an inverse relationship in your model. So your structure would look like :
user
NSString name,
int age,
NSSet records, ...
Record
NSDate date,
NSString name,
NSString event,
User user,
...
Then every time you create a new record, as well as adding the record to the users record set, set the user on the record object as well. This will make fetching the records a lot easier, and also it allows core data to keep data integrity:
"You should typically model relationships in both directions, and specify the inverse
relationships appropriately. Core Data uses this information to ensure the consistency of the
object graph if a change is made"
(Core Data programming guide)
Once this is setup, you should easily be able to get the records user by simply calling record.user
If you need multiple users for multiple records, then simply setup the relationship as a to-many relationship, and change the User user property to NSSet user.
If you don't want to change your models this much, then I'm a bit confused with your question. You say you want to get the records for a given user? If so, then why don't you simply call user.records as you have that relationship already defined? There's no need for an entire fetch request for that, let core data manage that one for you.
If you then want to filter the resulting Records based on the date, you can apply a predicate to the resulting array yourself, again without the need for a fetch request.
I have found what I've searched! I just needed to form a predicate using this
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"SELF = %#",delegate.currentUser];
EDIT
Well it seems that i was wrong :/ That only seemed to work.

CoreData's NSFetchrequest: Provide classname and propertynames moredynamically

In CoreData, when you want to query for an object, you have to specify the name of the entity and the names of the properties in strings like that:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"Name=%# AND Forename=%#", name, vorname];
I don't really like this approach, because in doing so, the IDE can't help me in scenarios like renaming one of the properties or renaming the classname.
Using this code-block, I made my call a little more dynamic by getting the classname by introspection:
NSString *classname = [[Person class] description];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:classname inManagedObjectContext:context];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"Name=%# AND Forename=%#", name, vorname];
Is there any way to get the name of the properties (Name and Forename) the same way?
I don't know how to handle properties dynamically.
Do you get my point? I'd be happy, if someone could point me into the right direction.
I've used plists in the resource bundle in this way, sort of-- basically, create an NSDictionary that maps keys that are your parameters to the names of the properties on the object at hand, then just name it MyClassname.plist for easy loading into memory. You'd have to write one up for every class you want this for, though. (and edit them if you change the property names)
Not sure if there is an easier way.
And, IIRC, you sub in the keys as so:
[NSPredicate predicateWithFormat:#"%k=%#",[plist objectForKey:#"param1"],[personObject valueForKey:[plist objectForKey:#"param1"]]]
I'm not sure I see a big advantage of entityForName:[[Person class] description] over entityForName:#"Person" (or maybe entityForName:PersonEntityName where PersonEntityName is just a constant). It works fine provided that the name of the class that represents an entity is the same as the name of the entity, but that's often not the case. It's quite common to use plain old NSManagedObject for simple entities, and that obviously doesn't match the entity name.
Likewise, if you're creating a fetch request, the names of the properties that you're interested in are usually already determined and unlikely to change, so specifying them by name is usually the easiest thing to do. You can get an array of properties from the entity description, but how will you know which property to use if you don't already know its name? And if you know the name of the property, there's not much sense in retrieving the property description just so you can get its name. ;-)
There are times when it's helpful to leave the entity and properties undetermined until the application runs, such as when you want to let the user specify the parameters of the search. That's clearly not the case in the example you gave, though, because you've given the format of the predicate.

Querying for all objects of multiple child entity types in Core Data

Given the following contrived example:
I would like to query my data for all objects that are either cats or dogs. I want the result set ordered by name regardless of species, so fetching all cats then fetching all dogs won't do. I want to do this in a single query.
One way to do this would be to add a petType field to Pet, give every record a petType value that identifies the sub-entity it belongs to, then query like so:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Pet"
inManagedObjectContext:myMOC];
[fetchRequest setEntity:entity];
// petType values: 1 = dog, 2 = cat, 3 = goldfish. Yuk.
NSPredicate *p = [NSPredicate predicateWithFormat:#"petType = 1 OR petType = 2"]
[fetchRequest setPredicate:p];
// etc...
But the mere thought of doing it that way makes me shudder. Is there a better way?
Update: Thanks to all those who've replied - there are some really good, well-thought out solutions here and I appreciate all of them.
To give this some context, the real data model is a little more complex than this (aren't they always), but it's pretty well organised. I've designed more than my fair share of data schemas in my time and I'm happy that the entities and their relationships are well considered. This issue has come about because (to extend the already shaky contrived example) the client originally wanted:
a view showing a list of all pets
a view showing a list of goldfish
a view showing a list of cats
a view showing a list of dogs
So far, so good. But they also want a view showing a combined list of all cats and dogs "because little girls like cats and dogs". (Initially it was cats and goldfish, for the same reason.) There isn't really a way to naturally group that subset of the concrete entities; it's really rather arbitrary.
So far, Dave Dribin's "abstract intermediate entity" approach seems like the cleanest solution, although in my case I think it would feel somewhat artificial; really the only way you could truthfully label the intermediate entity would be as "ThingLittleGirlsLike"! :)
As you've found out, you cannot use entity.name in your fetch predicate for SQLite stores (you can use it on other store types). You can add a petType, as you suggest, which works well enough, but makes me shudder, too. As an alternative, you could fetch all Pets, and then filter the fetched results based on entity.name. But that's a bit inefficient, since you're loading more entities than you need and your doing in-memory filtering that SQLite could be doing on your behalf.
I think the real question is: what are you trying to do? If you really need to fetch Cats and Dogs without Goldfish, then your model should reflect that. You could insert a FourLeggedPet abstract entity as a parent of Cat and Dog. Then, in your fetch request, use FourLeggedPet as the entity with setIncludesSubentities:YES.
Your managed objects already have a pet type: they have their entity.name. If you search for all Pet entities that have the entity name #"Cat" or #"Dog", that should do it, I think.
Example (typed quick and dirty into Xcode, so poorly designed):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSEntityDescription *catEntity = [NSEntityDescription entityForName: #"Cat" inManagedObjectContext: [self managedObjectContext]];
NSEntityDescription *dogEntity = [NSEntityDescription entityForName: #"Dog" inManagedObjectContext: [self managedObjectContext]];
NSEntityDescription *goldfishEntity = [NSEntityDescription entityForName: #"Goldfish" inManagedObjectContext: [self managedObjectContext]];
id cat = [[NSManagedObject alloc] initWithEntity: catEntity insertIntoManagedObjectContext: [self managedObjectContext]];
[cat setName: #"Mittens"];
id dog = [[NSManagedObject alloc] initWithEntity: dogEntity insertIntoManagedObjectContext: [self managedObjectContext]];
[dog setName: #"Rover"];
id fish = [[NSManagedObject alloc] initWithEntity: goldfishEntity insertIntoManagedObjectContext: [self managedObjectContext]];
[fish setName: #"Goldie"];
[[self managedObjectContext] save: NULL];
[fish release];
[dog release];
[cat release];
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName: #"Pet"];
NSPredicate *pred = [NSPredicate predicateWithBlock: ^(id evaluatedObject, NSDictionary *bindings) {
BOOL result = ![[[evaluatedObject entity] name] isEqualToString: #"Goldfish"];
return result;
}];
[fetch setPredicate: pred];
NSArray *entities = [[self managedObjectContext] executeFetchRequest: fetch error: NULL];
NSLog(#"Entities: %#", entities);
}
This logs the following:
2011-06-30 14:46:57.435
CDSubentityTest[5626:407] Entities: (
" (entity: Cat; id: 0x10025f740
; data: {\n name = Mittens;\n})",
" (entity: Dog; id: 0x1002914e0
; data: {\n name = Rover;\n})" )
Notice the expected disappearance of the goldfish.
I believe the approach you suggested would work nice. Having a petType could also allow you to define subtypes of Pets (i.e. "mammals", "birds", "reptiles", "fish", etc.). Of course, you could have abstract entities for each of these instead, but it's not clear how you would benefit from that either. It ultimately depends on how your application uses the model. Do you ever need to fetch all Pets? Do you plan for the possibility to get only Cats and Fish in a single fetch? Do you care about fetching data you won't immediately use?
Once you define the required classifications based on how you want to display/use the data, adapt the model to those classifications. Having a good looking, generic model, can often be a pain in the butt to use...
Predicates don't recognize entities so you can't construct a predicate to find them. (Update: This is only true of SQL stores.)
If you have your heart set on fetching by pet type then you don't have choice but to provide an attribute that will provide the pet type value and which will let predicates and fetches operate. However, you can make a much cleaner and safer version than the one you proposed.
Create a petType attribute for each entity (it can't be inherited in the entity but can be inherited in custom NSManagedObject subclasses.)
Then set the default value in the data model editor to the name of species e.g. cat, dog, goldfish etc. (If you use an inherited attribute, you inherit the default value as well.)
Override the setter method to do nothing effectively making the attribute readonly. (This can be inherited from a common superclass.)
-(void) setPetType:(NSString *) petType{
return;
}
Now finding all dogs and cats just becomes a matter of setting the fetch entity to Pet and then providing and array of pet type names for the IN operator.
NSArray *petTypes=[NSArray arrayWithObjects:#"cat",#"dog",nil];
NSPredicate *p=[NSPredicate predicateWithFormat:#"petType IN %#", petTypes];
While this will work, I think that Dave Dribin made the best point. This kind of hack is usually needed when you haven't properly refined your data model. If you need to group various pets by pet type then that grouping probably belongs to another real-world object, condition or event e.g. owner, which in turn should be modeled with relationships to specific pet instances. If you do that, then your grouping happens automatically and you don't have to mess around with all the above.

How to use the "ALL" aggregate operation in a NSPredicate to filter a CoreData-based collection

Based on the data model below
And based on user input I create a NSSet of managedObjects of entity Tag called selectedTags.
My problem:
[NSPredicate predicateWithFormat:#"ANY entryTags IN %#", selectedTags];
... this will return any Entry with at least one entryTag that is in the selectedTags set.
I want something along the lines of:
[NSPredicate predicateWithFormat:#"ALL entryTags IN %#", selectedTags];
... notice the only change is the "ANY" to "ALL". This illustrates what I want, but does not work.
To formulate the outcome I expect:
I'm looking for a solution that will return only Entries who's entryTags are all in the selectedTags list (but at the same time, if possible, not necessarily the other way around).
To further illustrate:
(tag)Mom
(tag)Dad
(tag)Gifts
(entry)she is a she.....(tag)mom
(entry)he is a he........(tag)dad
(entry)gifts for mom...(tags:)mom, gifts
(entry)gifts for dad.....(tags:)dad, gifts
If selectedTags contains "mom" and "gifts", then the entry "gifts for dad" will show up, since it has the tag "gifts". I'd rather have it not show :)
This is the definite answer so far:
[NSPredicate predicateWithFormat:#"SUBQUERY(entryTags, $tag, $tag IN %#).#count = %d", selectedTags, [selectedTags count]];
B-E-A-U-T-I-F-U-L.
Thanks to Dave DeLong.
How about using a compound predicate? As I understand you want to return all Entries that match a list of tags not just any of them. One approach would be to create a predicate for each tag, then AND them together using a compound predicate.
NSMutableArray *predicates = [[NSMutableArray alloc] init];
for (Tag *tag in selectedTags) {
[predicates addObject:[NSPredicate predicateWithFormat:#"ANY entryTags.tagName MATCHES %#", tag.tagName]];
}
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
This should achieve want you want. Then just set this predicate on your request.
You can't do what you want with a predicate.
The ANY and ALL operators apply to the entity being tested (in this case Entry) and not the contents of the collection (selectedTags). Either operator will return an Entry object that matches any single element of the collection. The ANY operator will return the first match it finds while the ALL operator will return all matches. In neither case will they return an entry that matches every element in the provided collection.
(It also looks like you are trying to use actual Tag objects in selectedTags. That will most likely not work either because object compares on classes without dedicated comparison methods usually fail. It is also slow. You need to compare attributes in predicates.)
Since you already have the Tag objects you want, to find the candidate related Entity objects, you just have to walk the Tag.taggedEntries relationship. Then you have to find the intersection of all the sets of Entity object to find only those Entity objects that are related to every selected Tag bject. Since there isn't an intersect collections operator, you need a loop.
if ([selectedEntries count]>=2) {
NSMutableSet *intersectEntries=[[NSMutableSet alloc] initWithCapacity:1];
for (int i=1; i<[selectedTags count]; i++) {
if ([intersectEntries count]==0) {
[intersectEntries unionSet:[[selectedEntries objectAtIndex:(i-1)] valueForKey:#"taggedEntries"]];
}
[intersectEntries intersectSet:[[selectedEntries objectAtIndex:i] valueForKey:#"taggedEntries"]];
}
}
(Note: I didn't test this but it should work.)
Now intersectEntries should contain only those Entry objects that are related to every selected tag.
I realized I could give something back here for the guidance that I have previously gotten. By using the code TechZen supplied I was able to come up with the following -- and for me highly valued -- piece of code:
- (NSArray *)unionSetOfObjectsForObjects:(NSArray *)objects {
NSMutableSet *unionSetOfObjects = [NSMutableSet set];
if (objects.count)
[unionSetOfObjects unionSet:[[objects objectAtIndex:0] valueForKey:#"key"]];
//unionSetOfObjects = [[objects objectAtIndex:0] valueForKey:#"key"];
if (objects.count > 1)
for (id object in objects)
[unionSetOfObjects intersectSet:[object valueForKey:#"key"]];
return unionSetOfObjects.allObjects;
}
If it is not immediately obvious what this code does:
It collects all the values (in my case objects) for the key key on all of the objects provided in the objects array.
This code just... tastes good, doesn't it?
The simplest way to do this:
[NSPredicate predicateWithFormat:#"entryTags IN %#", selectedTags];
You don't need the ALL clause. It's also documented here:
Predicate Programming guide
And as you can see in this post the user does it successfully (read the comments to the original question)
NSPredicate iPhone 3.2 SDK Core Data “IN clause”...