In my core data object model I have 3 entities with appropriate relationships so that MyObject can have many MyObjectProperties, and each property can have one MyObjectPropertyImage.
Given a myObject I want to fetch all the images.
I try to do it using the following predicate, however I get an empty array:
[NSEntityDescription entityForName:#"MyObjectPropertyImage" inManagedObjectContext:managedObjectContext];
[NSPredicate predicateWithFormat:#"ANY myObjectProperty.myObject == %#", myObject];
Any ideas?
When working with Core Data it's best to think of your entities as just that: entities in an object graph, instead of tables in a database. Therefore, you don't need to fetch entities related to others using a predicate. Instead, navigate the object graph using the relationships defined in the model. To get all the images related to myObject:
// assuming the relationships are called 'myObjectProperties' and 'myObjectPropertyImage', respectively
NSSet *allImages = [myObject.myObjectProperties valueForKey:#"myObjectPropertyImage"];
Note that this may trigger additional trips to the database if your object graph is not loaded in memory for your myObject entity. To avoid that, make sure you set the pre-fetching relationship keypaths in your fetch request for myObject.
I hope this helps...
Since you have a MyObject instance in hand and it has the relationship path of myObjectProperties-->ObjectProperty-->>PropertyImages you just need to traverse the relationships. It's easy to do this with valueForKeyPath:
Thusly:
NSArray *images=[myObjectInstances valueForKeyPath:#"myObjectProperties.propertyImage"];
(Note: I might have your attribute names wrong but you can get the idea.)
As general rule, you never fetch when have an object from the graph available. You fetch to "pick out thread" of objects matching the predicate and then to find all related objects you follow the thread/relationships to the related objects.
Related
I'm trying to learn how to use core data, but i got stuck on creating a relationship between two entities, i dont know if i'm looking at this at wrong angle, but basically, i have two Entities: "Listas" and "Tarefas". In my xcdatamodeld, i created a one-to-many relation between the objects, i just dont know how to set the correct relation when adding a "Tarefa".
To my question more clear, here is a image of what i have:
When adding a "Tarefas", object how do i relate it, with the passed "Listas" object?
Thank you!
It works the same as any other object attribute.
If you do not have custom NSManagedObject subclasses,
Listas *myListas = // passed in
Tarefas *newTarefas = [NSEntityDescription insertNewObjectForEntityForName:#"Tarefas" inManagedObjectContext:myManagedObjectContext];
[newTarefas setValue:myListas forKey:#"tarefaLista"];
If you do have custom NSManagedObject subclasses,
Listas *myListas = // passed in
Tarefas *newTarefas = [NSEntityDescription insertNewObjectForEntityForName:#"Tarefas" inManagedObjectContext:myManagedObjectContext];
[newTarefas setTarefaLista:myListas];
Keep in mind that since you have inverse relationships configured correctly, you only need to make the assignment on one side of the relationship. Core Data will make sure that the other side is also set. So above, I'm only setting a value for tarefaList, but the listaTarefa also gets a new value.
You could go:
Listas* l = //Your passed Listas
Terafas* t = //create your new Terafas
t.terafaLista = l;
It would be easier not to call your entities in a plural form (instead of Listas call it Lista). your code would make more sense.
As #TomHarrington mentioned, your inverse relationship will be automatically maintained.
I imagine that people will se this in future, so just in case, this is the code to filter the data based on the relationship between the two objects.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF IN %#", self.currentListas.listaTarefa];
[fetchRequest setPredicate:predicate];
self.currentListas is the Lista object passed by the segue.
Long question---thanks in advance for your time. After saving new managed objects, I am finding them added to a relationship on another object in my core data database---one for which my code calls no setter method and that has no inverse relationship. I have pored over the code and used logs to isolate the occurrence the best I can, but I'm encountering bizarre behavior I cannot explain (or fix).
More specifically:
I have an entity called PendingSyncTracker. It simply has one relationship, objectsToSync. I have not yet added any line in my code to call a setter method on this relationship. It is a to-many relationship. It points to BaseEntity. For the "Inverse" option, I have selected "No Inverse Relationship."
When I load a particular table view, 3 objects are downloaded from a server and then parsed into managed objects and saved. By the time the table view begins loading cells, 2 of those 3 objects will mystifyingly be present in the objectsToSync relationship.
I have used NSLog all over my code to figure out exactly when these objects can first be found as members of the objectsToSync set.
NSSet *objectsToSync = [[[SyncEngine sharedEngine] fetchClassNamed:#"PendingSyncTracker" withPredicates:nil][0] valueForKey:#"objectsPendingSync"];
NSLog(#"PendingSyncTracker objectsToSync set (%lu objects): %#", (unsigned long)[objectsToSync count], objectsToSync);
The answer to when they first appear in the set actually varies depending on where I do/don't place those 2 lines of code!
The objects are never found on the relationship before the managed object context is saved in the course of saving my 3 new core data objects.
If I don't use those 2 lines till I'm back in the Table View Controller that sent the new objects off to the Sync Engine to be stored locally (where the MOC is accessed and saved), then the log will there reveal that 2 objects have been added to the relationship.
If I use those 2 lines immediately after saving the MOC in the Sync Engine, then the logs will indicate (both there and back in the TVC) that only 1 object has been added to the relationship.
If I use those 2 lines immediately before and after saving the MOC (and back in the TVC), then all 3 logs will reveal that the relationship contains an empty set.
I also have those 2 lines at the beginning of cellForRowAtIndexPath. Regardless of prior logs, that log will always indicate that 2 objects have been added to the relationship.
All 3 of the managed objects that are created in the Sync Engine are stored as entity types that are subEntities of BaseEntity (to which the objectsToSync relationship points). The 2 types that get added to the relationship are each defined to have a reciprocal relationship, but with a different object, not PendingSyncTracker (although the different object is a subEntity of BaseEntity!).
So.. what explains these observations? How are these objects getting added to the relationship?
UPDATE:
- (NSArray*) fetchClassNamed:(NSString*)className withPredicates:(id)parameters;
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// set predicates
if (!(parameters == nil)) {
[fetchRequest setPredicate:parameters];
}
NSError *error;
NSArray *fetchedResults = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return fetchedResults;
}
First, what does [[[SyncEngine sharedEngine] fetchClassNamed... do? Just a guess but it is doing something with KVC to set the relationship for you.
Also, you should always, always, always have an inverse relationship. Even if you never use it, Core Data does. Not having an inverse can lead to lots of issues, including but not limited to performance problems and potentially data corruption.
Add an inverse relationship and update your question with what -fetchClassNamed... does.
I have a Core Data model in my app with a 'Notebook' entity.
There are two ways I could access one or many 'Notebook' instances:
By executing an NSFetchRequest with a predicate asking for an instance with a specific 'title' or 'index' attribute.
By adding an 'AppData' entity in my Core Data model, forgo the 'index' attribute in 'Notebook' and instead have a to-many ordered relationship from 'AppData' to 'Notebook'. Then I would create an 'AppData' instance on the first app launch (and at every launch after that I would fetch request that one and only 'AppData' instance) and access all 'Notebook' instances through its to-many relationship. To access a notebook by title I would use indexOfObjectPassingTest or fast obj-c enumeration, to access by index I would use objectAtIndex.
It is much easier to query an 'AppData' instance for 'Notebook' instances and their attributes than setting up a fetch request to the managed object context each time.
However, which method would be faster? would one method use more memory or stay in memory more time? I read that objects in to-many relationships are loaded lazily, but when would an object in that set be loaded? when would it be unloaded?
The AppData entity seems redundant. You have to fetch it first, so why not fetch all Notebook instances instead? The filtering (by title, other ID attributes) would be the same in both scenarios.
A fetch request does not have to be painful:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Notebook"];
NSArray *fetchedObjects = [moc executeFetchRequest:fetch error:nil];
Also, depending on the amount of data you can filter these in-memory, or include a predicate in the fetch request.
Definitely, regardless of the scenario, forgo your idea of the "index attribute". Core Data is an object graph, not a database. The only justification for this could be if you need to sync with some external framework or data store that uses unique identifiers.
For convenience, you could create a method in a category of your managed object.
+(NSArray*) appNotebooks {
NSArray * fetchedResults = // fetch the appropriate entities
return fetchedResults;
}
Then you would use it like this:
Notebook.appNotebooks;
I have a model, where I have an entity called "Category". This Category has a one-to-many relationship called "Products". How do I set up the fetch request to only get the Category entities WITHOUT the products?
If you query against Category, product elements are loaded as faults. This is the default behavior of CoreData.
On the contrary if you use - (void)setRelationshipKeyPathsForPrefetching:(NSArray *)keys, you can load (pre-fetch) products when the request is executed.
From Core Data App
Faulting reduces the amount of memory your application consumes. A
fault is a placeholder object that represents a managed object that
has not yet been fully realized, or a collection object that
represents a relationship:
A managed object fault is an instance of the appropriate class, but
its persistent variables are not yet initialized. A relationship fault
is a subclass of the collection class that represents the
relationship. Faulting allows Core Data to put boundaries on the
object graph. Because a fault is not realized, a managed object fault
consumes less memory, and managed objects related to a fault are not
required to be represented in memory at all.
and
Pre-fetching is in effect a special case of
batch-faulting, performed immediately after another fetch. The idea
behind pre-fetching is the anticipation of future needs. When you
fetch some objects, sometimes you know that soon after you will also
need related objects which may be represented by faults. To avoid the
inefficiency of individual faults firing, you can pre-fetch the
objects at the destination.
Edit
If you need to count the number of products for a specific category, use - (NSUInteger)countForFetchRequest:(NSFetchRequest *)request error:(NSError **)error using a request like the following:
NSFetchRequest* request = // set up a request for Products
[request setPredicate:[NSPredicate predicateWithFormat:#"toCategory == %#", currentCategory]];
// count for fetch request here...
where toCategory is the inverse relationship from Products to Category and currentCategory is the category you have.
Take a look at the docs for NSFetchRequest
You can set 'includeSubentities' and 'returnsObjectsAsFaults' to restrict what data comes back from Core Data with your model (Product)
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
I am learning coreData and I am new it, I have created a one-to-many relationship of boss and employee, (i.e one boss and many employees). So I am showing all the bosses on firstTableView and when the user clicks on the cells, he can view the employees assigned to each boss and also he can add employees to any particular boss. Now I want to reorder the boss cells. So how it should be done?
Edited based on the discussion below
- (void)insertNewObject:(NSString *)fileName
{
Boss *bossName = [NSEntityDescription insertNewObjectForEntityForName:#"Boss" inManagedObjectContext:self.managedObjectContext];
[bossName setName:fileName];
NSManagedObject *lastObject = [self.controller.fetchedObjects lastObject];
float lastObjectDisplayOrder = [[lastObject valueForKey:#"displayOrder"] floatValue];
NSLog(#"%f",lastObjectDisplayOrder);
[bossName setValue:[NSNumber numberWithDouble:lastObjectDisplayOrder + 1.0] forKey:#"displayOrder"];
// Save the context.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[Specific Answer To Updated Question]
It would be either ....
[self.boss insertObject:newEmployeeObject inEmployeesAtIndex:0];
Which is a core-data generated method that is part of your Boss subclass if you choose to create subclasses from your model. Or....
NSMutableOrderedSet *employees = [self.boss mutableOrderedSetValueForKey:#"employees"];
[employees insertObject:newEmployee atIndex:0]
It's not that intuitive I know, you can't just make a mutable copy, you have to get a special proxy object from mutableOrderedSetValueForKey.
[Original General Answer]...
Core-data now has the ability to use "Ordered Relationships" which you can specify in your model. If you do so, relationships in your object model will be represented by a new class NSOrderedSet which is a hybrid of an NSArray and an NSSet. By re-ordering the objects in this relationship object and saving the context you will reorder the objects in the database and they will maintain their new order. This kind of ordered relationship tends to be used when there isn't natural ordering attribute on the object. For instance the order simply represents the users preference for ordering a list in the UI.
If on the other hand you have an attribute on one of your objects that describes the order for a collection then you can use that attribute to order the results of an NSFetchRequest by specifying the Sort Descriptors. The value of the attribute would specify the position the object would be in in the results of the NSFetchRequest.
If you are using Ordered Relationships you would need keep the order of the NSOrderedSet for that relationship and the UITableView in sync. If the change was driven from the UI then you respond to the UITableViewDataSource delegate methods such as - (void)moveRowAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex and use the information provided to move the corresponding object to it's new position in the core-data relationship either by using the proxy object from mutableOrderedSetValueForKey: or the Core-data generated accessors of a generated subclass.
If the change to order were driven from the data side you would use the methods on UITableView such as insertRowsAtIndexPaths:withRowAnimation: and moveRowAtIndexPath:toIndexPath: to sync the rows in the UITableView with the changes you were making in the data.
If you are using NSFetchRequests you have a similar task. In this case you respond to user driven changes in the order by updating the sort attributes on your objects to match the new order that is described by the UITableView through the UITableViewDataSource delegate. Or if the ordering changes are starting at the data side you update the UITableView through it's methods to match the changes you are making to the sort attributes on the data. In this case you will be working with the results from the NSFetchResults as an NSArray, you would also have to keep that object in sync until the next time you ran the NSFetchRequest. You could use the same sort descriptor to sort the array, or create an NSMutableArray and use it's methods to move the data to match the table.
Finally if you are using NSFetchRequest you may like to look at NSFetchedResultsController It's job it is to simplify task of syncing a sorted NSFetchRequest and a UITableView. There is good sample code for this in the documentation. In this case you may find that the ordering of the data will take care of itself. For instance say your table is ordered by "assignment date" (i.e. the date at which an employee was assigned to a boss) then simply creating the objects with the correct information would trigger the correct results in the table.
Please note that ordered relationships do not work with iCloud. However in my opinion iCloud doesn't work anyway so that's not a problem.