CoreData's NSFetchrequest: Provide classname and propertynames moredynamically - objective-c

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.

Related

NSPredicate with Core Data

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

Design Decision: Adding new field to Core Data model and sorting with it

I'm using NSFetchedResultsController to display a table of my NSManagedObject data.
Up to now, I've been using the name property on my objects to sort them:
NSFetchRequest* request = [[NSFetchRequest alloc] initWithEntityName:[Item entityName]];
NSSortDescriptor* nameSorter = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
request.sortDescriptors = #[nameSorter];
self.frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.moc
sectionNameKeyPath:#"nameInitial"
cacheName:nil];
Note that my sectionNameKeyPath is different from my sort request. As in this answer, I use a transient property on Item, called nameInitial. Its getter just reads name and returns the first letter.
So far so good. But now, I want to add a special condition: if the first word of the name is 'the', I don't want to sort by that. I want to sort by the first letter of the 2nd word. I can't do this with a transient property because now, the NSSortDescriptor on the fetch request will give a different order than the sectionNameKeyPath, which makes NSFetchedResultsController barf.
So I added a nameInitial field to Item and performed a lightweight migration. Now, I can add a NSSortDescriptor using this new attribute. I just have to populate it with the right letter. This is where my problem comes in: What do I do with the objects I already have in the DB, for which the nameInitial attribute is nil? As far as I can tell, these are my options:
Write a code that executes upon the first launch of the new version of the app, reads all the name fields and updates nameInitial appropriately.
Use awakeFromFetch to automatically update nameInitial for each object as it is loaded.
Override the getter of nameInitial, so it updates itself if it's nil.
I don't particularly like any of these options. The first one isn't elegant at all, and the last two mean either the awakeFromFetch or the getter will have unexpected side-effects. Is there a better way to do this that I'm missing?
You shouldn't do any of those. You should be writing a migration which processes this instead of using a lightweight (auto) migration (which can only fill the values in as nil).
Technically, your suggestions will work, but they aren't 'correct', will run slower and will be a maintenance burden in the future.
From your comment, the best option then is your first suggestion. You don't have many choices - use the built in migration processing or write your own version check and migration logic.

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.

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.

CoreData many-to-many relationship NSPredicate Exceptions

I'm trying to model a Person/Team relationship. It's a many to many relationship since a person can belong to multiple teams and a team can have multiple people. As suggested by the doc I created an intermediate entity called TeamMember. I am now trying to run a query to see if I have a list of people, whether a pre-existing Team already exists for them so I'm not storing duplicate Teams in the database
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Team"
inManagedObjectContext:[pm managedObjectContext]];
[request setEntity:entity];
NSPredicate *predicate = nil;
predicate = [NSPredicate predicateWithFormat:#"ALL %K IN %#", #"teamMembers.person", players];
players is an NSSet of people that I'm trying to search
I'm getting the following exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unsupported predicate ALL teamMembers.person IN { (entity: Person; id: 0x1334470 ; data: {
Ideally I would like them to match exactly and not just do an IN as well.
Any help would be greatly appreciated
First, seeing your data model would be helpful but I am guessing you are doing something like:
Person <-->> TeamMember <<--> Team
I realize you saw this in the docs (which tend to make simple things complicated for unknown reasons), I have to ask; why are you doing this? Unless there is data specific to that TeamMember object then you can just do:
Person <<-->> Team
Second, your predicate is incorrect and likely unnecessary. Since you already have a reference to the person, you can query the teams that the person belongs on directly via:
NSSet *teams = [person valueForKeyPath:#"teamMembers.#distinctUnionOfSets.team"];
Which will give you back a set of Team objects. If you, however, remove the TeamMember object then you can simplify this query with:
NSSet *teams = [person valueForKey#"teams"];
Because Core Data uses NSSet as opposed to NSArray you don't even need to worry about duplication at this level because if you try to add the same team to the set more than once it will be ignored.
And to round out the answer, the issue with your predicate is that you cannot mix IN and ALL together in a predicate against Core Data. This is a known limitation. Therefore I recommend going the approach I described above.