I'm fairly new to iOS/Cocoa and I have a very fundamental question about core data. I was looking over the internet to find a appropriate answer/solution but I wasn't able to.
So the question is how do you handle uniqueness in core data? I know that core data is not a database, its something like an object graph. Lets assume we have an entity called 'User' with the attributes 'id' and 'name' and a few relations to the other entities. Now we want to update the name of a specific user (e.g. a web service gave us the id and the new name).
This was the way I have done that before:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"id == %#", anId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *user = [results lastObject];
But than I've heard this is bad practice. Is it because fetch requests with predicates are very slow? I can't imagine that this is such a big deal. As far as I know there is no other way to get a unique object rather than go over each object and checking for equality..
Would it be more efficient to fetch all objects of the entity, put them in an array and looping through it manually (instead of the fetch request)?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *resultUser;
for(User *user in results){
if([user.id isEqual:anId]) resultUser = user;
}
Please help me finding the right path and thanks.
If you have an index for the property that you're fetching (there's a checkbox in the model editor), it's definitely a lot more efficient to use an equality predicate for fetching. Actually, it's never more efficient to fetch all objects and iterate over them in memory, but an index makes the difference more significant.
You're not fetching a unique object but rather objects containing a notionally unique atrribute value.
All managed objects are unique instances but nothing in Core Data enforces that the attribute values are unique. You could in principle have an arbitrary number of unique managed objects all which had identical attribute values. Only relationships enforce a unique position in the object-graph.
There's no particular reason not to fetch a particular object that contains a particular value if that is what your app requires.
I think what you've read is warnings against trying to cram SQL-like key values into entities and then to try and link managed objects together with those keys using predicates, for example doing something like:
EntityOne{
sqlKey:string
}
EntityTwo{
sqlKey:string
}
… and then trying to relate objects of the two entities with predicates.
(anEntityOneObject.)sqlKey == anEntityTwoObject.sqlKey
… instead of just setting a relationship in the data model.
EntityOne{
two<-->EntityTwo.entityOne
}
EntityTwo{
one<-->EntityOne.two
}
… and just finding the related objects with AnEntityOneObj.two.
Related
My app sends a get request to a server with a date (date of last update) to update its content (2 entities in core data, no relationships, all attributes are strings)... most of the time it only receives new content but sometimes it also receives the old content that needs to be updated instead of just inserted. 1 table is pretty straight forward I just get the ids (id from server not form core data) of the items that are going to be updated in an array and I make a fetch of those items and then delete them. After that I insert the updated items as they were new. This is how I delete them:
-(void)deleteOfEntity:(NSString*)entityName theItemsWithIds:(NSArray*)ids{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext: [self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"(id IN %#)", ids]];
NSError *error;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if(fetchedObjects && [fetchedObjects count]>0){
for (NSManagedObject* toDelete in fetchedObjects) {
[[self managedObjectContext] deleteObject:toDelete];
}
}
}
because the attribute name which identifies each item is the ID as usually. But the other table needs 2 attributes to identify items, like a composite key. How do I build up the array of "ids"? an array with arrays of 2 values indicating the composite key? and the predicate? I just want to know if it is possible to do this efficiently, if not I can always fetch all the items and check 1 by 1 but for that I need a for inside another for and that is to ugly. Any help is appreciated.
When you designed the database you should have created a unique key field, even if it is just a composite of the two values. That would have made this question go away.
However, to solve the problem now you need to do a fetch on one key similar to what you have above and then loop over the second key. However, you do not need to do a loop within a loop. You can use a second NSPredicate against that returned array to get the objects to modify.
Less ugly and quite efficient since you are only going to disk once and the second filter is happening in memory.
Update
#DuncanGroenwald is correct that you still must loop through every object, but there is looping and there is looping.
A developer writing a for loop and then doing a string compare inside of that for loop is significantly less efficient then letting the frameworks perform the same option. How? With a NSPredicate against the array:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"someValue IN %#", idArray];
NSArray *results = [origArray filteredArrayWithPredicate:predicate];
If you test both options, the predicate will run significantly faster.
Well what I did is to create another attribute named "identifier" which is a stringWithFormat:#"%#%#",key1,key2, it doesn't matter the extra string in coredata because it suppose to have just a few managed objects in that entity
I have 1 entity in my database "Message" with the values MessageID, messageText and i want to read every row of Core Data, make an object of my class "Message" and put the new object into an array.
It's the first time I'm using Core Data and I don't quite get it yet, how I manage to do that.
Create a fetch request for the entity you wish to retrieve. Don't give it a predicate, set whatever sort descriptor you want.
Execute the fetch request in a managed object context and it will return an array of all the objects of that entity.
This is purposely just a descriptive answer, you can find the specifics of how to do this from the Core Data introductory documentation; you are new in Core Data and this is a good way to learn it.
Also - don't think of Core Data in terms of rows of data that you turn into objects. It's an Object-Relationship graph. It stores the objects of entities and their relationships between them. You don't turn the "rows" into objects, you get the objects back directly.
The response of #Abizern with code :
NSManagedObjectContext *moc = // your managed object context;
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Message" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// You can also add a predicate or sort descriptor to your request
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}
I know there have been several discussions about this but none of them resolved my simple problem.
I have an Entity called Character and inside there are 4 columns:
character_id, episode_id, title, desc
there can be several same character_ids values but with different episode_id.
When I perform fetch\select I do it for whole table and wishes to get it distinctly by character_id. so this is what I do:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
// Add a sort descriptor. Mandatory.
if(sortDescriptors != nil) {
[fetchRequest setSortDescriptors:sortDescriptors];
}
fetchRequest.predicate = predicate;
// Required! Unless you set the resultType to NSDictionaryResultType, distinct can't work.
// All objects in the backing store are implicitly distinct, but two dictionaries can be duplicates.
// Since you only want distinct names, only ask for the 'name' property.
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = [NSArray arrayWithObject:[[entity propertiesByName] objectForKey:#"title"]];
fetchRequest.returnsDistinctResults = YES;
NSArray *fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
The 'fetchResults' array contains 3 out of 10 rows which is the right result!
The problem: None of the object within the array is accessible.
If I try the following:
NSDictionary item1 = [fetchResults objectAtIndex:0];
NSString *title = [item1 objectForKey:#title"];
I get an exception!
What am I doing wrong?? how can I translate back the dictionary into NSManagedObjects??
Thank you!
First, when using Core Data you should not use foreign keys. Rather, it is preferable to use Core Data's relationships and let the framework deal with the ids in an opaque manner. Maybe you are synching with a web service and need to keep track of the ids but your program logic actually should not depend on it.
Second, if you need an object, it is really better to use the NSManagedObjectResultType rather than the NSDictionaryResultType. You can still obtain distinct results. If you are not experiencing performance issues, this is the preferred pattern. The code is also much more readable.
Your data structure would be this, with a many-to-many relationship:
Character <<--->> Episode
All characters of an episode or all episodes with a certain character is simple. These will be "distinct" results dictated by the logic of the data model:
NSArray *allCharactersInEpisode = episode.characters;
NSArray *allEpisodesWithCharacter = character.episodes;
To select all characters of all episodes you just select all characters. Much simpler than a "distinct" query.
I asked a question yesterday where I really should have started with a simpler example. Having distilled my question to the basics, I've managed to solve my problem using existing SO questions and answers.
I'm summarising my question here (and providing my own solution) because I don't think there are any posts that explain this clearly enough. Being new to Core Data and struggling to get away from SQL concepts, I'd welcome feedback on how appropriate my solution is, and if there are better ways of modelling the problem.
Question
Given the following object model, that has three entities A, B and C, each linked by to-many relationships:
How is it possible to identify the parent A entities, that have grandchildren C entities with a particular attribute? By example, using these sample entities and their relationships:
How can I find our which entity A s have child entity C s with the Tag:Yes?
Solution
I've been able to achieve this is using the SUBQUERY keyword of NSPredicate. Here is the code snippet that worked for me (assuming you've set up your managed object context etc):
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityA" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:#"(0 != SUBQUERY(child, $a, (0 != SUBQUERY($a.child, $child, $child.tag == %#).#count)).#count)", #"YES"];
[fetchRequest setPredicate:predicateTemplate];
NSArray *test = [context executeFetchRequest:fetchRequest error:&error];
Breaking the all important NSPredicate string onto several lines:
(0 != SUBQUERY(child,
$a,
(0 != SUBQUERY($a.child,
$child,
$child.tag == %#).#count)
).#count
)
The important part is we are selecting the EntityA to work from and then in the nested subquery working through the child relationships of the entities. I expect this can be repeated for several depths. Not the most intuitive thing to put together... but it works. Comments welcome.
I've never tried to use a subquery before, that's very cool and good to see an example of it. You should add the solution as an answer to the question actually!
I would have probably done as Abizern suggested, since this is a tree-like hierarchy and its easier to traverse up the tree using to-one relationships.
In code this looks like:
NSManagedObjectContext *moc = APPDELEGATE.managedObjectContext;
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:[NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"tag = YES"]];
NSError *fetchError = nil;
NSArray *children = [moc executeFetchRequest:request error:&fetchError];
children is an array of EntityC objects that match the predicate. The next step is getting a set of unique EntityA objects that are the "grandparents" of these. We can take advantage of key-value coding here:
NSArray *grandParents = [children valueForKeyPath:#"parent.#distinctUnionOfObjects.parent"];
In this case, for efficiency, we'd probably want to prefetch the parent.parent keypath during our initial fetch request:
[request setRelationshipKeyPathsForPrefetching:#[#"parent.parent"]];
Hope this helps!
If you've followed the Core Data Guidelines and have set up reciprocal relationships you could try searching from the bottom up.
Fetch the Entity C objects that pass your required predicate and then get the Entity A object by going up the parents. No need for nested subqueries.
Edited to add
You can get the parent of C (which is an object of B) from the parent relationship. And from there you can get the parent (which is an object of A) form the parent relationship.
It's in your Core Data Model Diagram.
In the FRC documentation, it says that it is intended to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
I am trying to setup my Core Data stack to handle the storage and retrieval of some VERY basic data. Can I not use FRC? I need to display a value set in the db to a UILabel, what is the best Core Data method to use for that?
I have a core data book I am trying to get through, but the going is rough, so any help here would go a long way. Thanks!!
NSFetchedResultsController may be overkill if all you want is to fetch an object and display one of its attributes in UILabel. Take a look at NSFetchRequest and start with something like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:entityDescription
inManagedObjectContext:managedObjectContext]];
NSPredicate *predicate = /* define predicate here */;
[fetchReqest setPredicate:predicate];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
If you want to sort your results, read up on NSSortDescriptor. You will need to set sort descriptors prior to -executeFetchRequest: call.
Your results will be in 'results' array - and they should include NSManagedObjects that you can get attribute values from.