NSFetchRequest not catching objects that have a changed property - sql

I have run into a weird problem with CoreData on MacOsX 10.6 using an SQL store. I have an NSManagedObject subclass called Family with attribute name and a relationship personList connected to another NSManagedObject subclass called Person with attribute firstname and inverse relationship family. A Person has only one family, and a family can have several Persons.
Say I have a Family object family pointing to the family 'Doe' with 2 Person (John and Jane) connected to it and I do the following request:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Person" inManagedObjectContext:managedObjectContext]];
[request setPredicate:[NSPredicate predicateWithFormat:#"family.name=%#",[family name]]];
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
I get an array of size 2 with the 2 persons: Jane and John, with Family name Doe. Now, if I update the Family using its synthesized accessor, in my case:
[family setName:#"Wheat"]
I cannot after get the list of Person using the same fetch request. The results is an [array count] of 0.
If I change the predicate to the following line, it works again:
[request setPredicate:[NSPredicate predicateWithFormat:#"family=%#",family]];
So it is as if the Predicate is not using the updated version of the property name of the family, even though I have the NSFetchRequest set to the defaults (so includesPendingChanges returns YES). That makes no sense to me. It seems that the NSFetchRequest finds the family object, but fails to see that its value family.name has been updated, not saved, and is in the managedObjectContext in memory. Of course, if I save the store, then it works again.
Any idea? I have been through the Mac documentation and cannot see why this would fail.

I think the key here is understanding the fetch request. It retrieves data from the persistent store, so clearly, if you did not save to the persistent store, it will not find that data. The situation you describe is entirely logical if you take that into account.
From the Core Data Programming Guide:
You cannot fetch using a predicate based on transient properties
(although you can use transient properties to filter in memory
yourself). Moreover, there are some interactions between fetching and
the type of store—for details, see “Store Types and Behaviors.” To
summarize, though, if you execute a fetch directly, you should
typically not add Objective-C-based predicates or sort descriptors to
the fetch request. Instead you should apply these to the results of
the fetch. If you use an array controller, you may need to subclass
NSArrayController so you can have it not pass the sort descriptors to
the persistent store and instead do the sorting after your data has
been fetched.

To summarize, I have been testing thoroughly my code and here is how I perceive the limitations of CoreData regarding Fetching and objective-c predicated (ie the dot notation).
If an object has been access by the Objective-C program and if one of its property or relationship has been modified, any NSFetchRequest with a predicate using a dot notation will return the structure of the SQL store, hence the results will be erroneous.
In the case of the trivial Family and Person example, if you have a link to a Family and change its name, any query made on the Person NSEntity cannot include a predicate with the following query item
#"family.name=%#"
It will indeed query using the family name in the SQL store. However, the following query will work after such a change:
#"family=%#"
Indeed, the NSFetchRequest will still retrieve the info in the store, but since the structure has not changed, it will replace the objects retrieved by those in Memory, so a subsequent test to [family name] will return the updated name.
With care, you can use nested Predicate such as:
#"person.family.name=%#"
As long as you can guarantee that all the objects that have the property person, have not their family altered, nor their name altered. If it's not the case, then you can at best call
#"person.family=%#"
Or if you can't guarantee that all the Family objects are untouched, only
#"person=%#"
Of course, an alternative is to systematically SAVE: the NSManagedObjects to the persistent store every time you make any change, so all properties are updated and then all the above notations would work. There are times however when you do want to prevent savings and force the customer to change it's document only if he wishes (think about Word, Excel, Picture tools, etc..). Hope this is of help.

If by "using the same fetch request", you mean using the very same instance of the fetch request that you constructed the first time, then this is no surprise. The predicate you applied is "family.name = Doe". Once the family's name is "Wheat", the fetch request's predicate no longer matches it, because "Wheat" != "Doe".
To retrieve the family after changing its name, you would need to create a new instance of NSFetchRequest using a predicate matching the new family name.
If by "using the same fetch request", you mean using a different fetch request constructed using the same code, well, then I would think about #Mundi's answer.

Related

MagicalRecord get all entities from one to many relationship where relationship id is x

Say I have an entity called Message, and an entity called Group. Every group can have many messages (one to many relationship).
How do I use MagicalRecord to get all the messages of a group, WITHOUT first loading the group?
NSArray *entities = [Message MR_findAllSortedBy:#"groupID:x" ascending:NO];
I thought of just adding an attribute groupID to message, but it seems very wrong... (since obviously the relationship is saved in my db in some other way)
Probably something like:
NSArray *messagesInGroup = [Message MR_findAllSortedBy:#"group.groupID" ascending:NO]
The sortedBy parameter just translates into the [NSSortDescriptor sortDescriptorWithKey:ascending:] method. According to the documentation, that first parameter is a keyPath, so as long as it's valid (ie. exists), it will work for you.

What is the best way to reference a core data entity?

I have been reading and working a lot with core data recently, and I love the way it implements the data storage.
However, there is one thing I am still not able to resolve in my app.
On my data model, I have an entity which represents a chunk of text the user have inserted in a text field. I want to be able to give the user the possibility to embed this chuck of text into another text field by referencing it.
If I was working with a database directly, I would do something like this:
The user adds a chunk of text to the database. It happens to have the row index 17.
The user goes back to the main editor where it can reference chunks of text from the database and inserts a pattern such as {chunk.17} where 17 is the row index in the database.
The user clicks a "parse" button, making the app query the database for this row and replace the string {chunk.17} for the text chunk stored in there.
Since Core Data has no such thing as an auto incremented index, I am not sure how to create a similar behavior without much work. Any ideas are appreciated!
It sounds like you'll need to add a property (such as "id") to your entity that has some unique value (there are lots of discussions on Stackoverflow about how to generate a unique ID/value for an object). Once you have that, you can reference this value using an NSPredicate:
NSFetchRequest *req = [[NSFetchRequest alloc] initWithEntityName:#"Demo"];
// A predicate is used as a limited substitute for a 'where' clause. In this case, we're specifying that
// the result set should only contain entities whose id is 'chunk.17'.
[req setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE[c] %#", #"id", #"chunk.17"]];
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:req error:&error];

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.

Core Data – NSManagedObjects in instance variable update

Say that I perform a fetch of all entity Employee objects by doing: NSArray *employees = [context executeFetchRequest:request error:&error];
Then I set an instance variable in my class by doing: self.allEmployees = employees;
Then later in my app I will do some modifying to my employee objects but not through accessing self.allEmployees. I'm modifying them from another class.
Will my self.allEmployees array be updated to the changes I've made from another class to the employee objects? Or will my self.allEmployees array be in the state that the employee objects were when I first performed the fetch? (I'm suspecting the later)
The array you get back from the fetch request holds references to live managed objects. Unless you change a different fetchLimit or batchSize you get an array with as many fault objects as the query would return objects.
When you access a property of one of these fault objects CoreData retrieves the actual data for all the properties transparently and returns these to you.
A managed objects always has its own most recent value that was last called save on. So if you do a modification on the self.allEmployees you need to call save on the MOC and this will broadcast the changes to all other emoployee objects.
This is also the reason why KVO works on NSManagedObject properties, because they get notfified of all saved changes that affect them.
If you alter the objects you receive from the fetch, and don't copy them, then yes.
Those are all pointers.
So you shouldn't need to do another fetch request.
If you change values of an employee, the pointer won't change.
The only thing that changes is the instance variables, or properties.

How to get the ID of an object saved to Core Data's managed object context?

I have this code:
NSEntityDescription *userEntity = [[[engine managedObjectModel] entitiesByName] objectForKey:#"User"];
User *user = [[User alloc] initWithEntity:userEntity insertIntoManagedObjectContext:[engine managedObjectContext]];
and I want to know the id of the object inserted to the managed object context. How can i get that?
Will that id remain the same for that object's lifetime or will it persist to the sqlLite database beneath this and be something that can be used to uniquely identify it during a fetch operation (my ultimate goal).
Any help appreciated // :)
If you want to save an object's ID permanently you need to:
Save the object into the context so that the ID changes from a temporary to a permanent ID.
Extract the URI version of the permanent ID with -[NSManagedObjectID URIRepresentation]. That returns a NSURL you can store as transformable attribute in another managed object.
You can get the object by using -[NSPersistentStoreCoordinator managedObjectIDForURIRepresentation:] to generate a new NSManagedObjectID object and then use -[NSManagedObjectContext objectWithID:] to get the actual referenced managed object.
The URI is supposed to identify a particular object in a particular store on a particular computer but it can change if you make any structural changes to the store such as migrating it to a new model version.
However, you probably don't need to do any of this. ObjectIDs play a much smaller role in Core Data than they do in other Data Model systems. Core Data maintains an object graph that uniquely identifies objects by their location in the graph. Simply walking the graph relationships takes you to a specific unique object.
The only time you really need ObjectID is when you're accessing object across two or more persistent stores. You need them then because relationships don't cross stores.
Read up on "managed object IDs" in the Core Data Programming Guide
You can get the object id from the object with something like:
NSManagedObjectID *moID = [managedObject objectID];
First, you are constructing your objects in a non-preferred manner. Generally you should:
User *user = [NSEntityDescription insertEntityForName:#"User" intoManagedObjectContext:[engine managedObjectContext]];
Second, when you create the object it will get a temporary id which you can access via [user objectID] as David mentioned. Once you save the context then it will get a new "permanent" id.
However this id can and does change over the lifetime of the entity (although not the instance). Things like migrating the data can cause this id to change. However, between saving the context and exiting the application the id will remain the same.