iOS - Core Data - Delete records using relationships and fetch request - ios

Overview:
I have an iOS project in which I am using core data
I have an Employees entity and a Department entity.
1 department can contain many employees
So the entity Department has a "to many" relationship with the entity Employees, the relationship is called employees and the reverse relationship is called whichDepartment
Aim-1:
I want to delete all the employees in a specific department
Questions:
a) is the following correct, or would it cause mutation or some problems ?
b) is this is the correct way to do it ?
Pls Note - removeEmployees is a method that was auto generated while creating the subclasses of the entities
- (void) deleteAllEmployeesForDepartment: (Department*) requestedDepartment
{
[requestedDepartment removeEmployees:requestedDepartment.employees];
}
Aim-2:
I want to delete the employees based on some condition
I am deleting objects inside a fast enumeration loop for the fetched records
Questions:
c) Is the following correct, or would it cause some mutation ?
d) Is it like modifying the object in fast enumeration ?
e) Is there a better way to do it ?
Pls Note - removeEmployees is a method that was auto generated while creating the subclasses of the entities
- (void) deleteAllType1EmployeesWithDepartment: (Department*) requestedDepartment
{
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"type == %i AND whichDepartment ==%i", 1, requestedDepartment.departmentID];
NSError *error;
NSArray *listOfEmployeesToBeDeleted = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for(Employees *currentEmployee in listOfEmployeesToBeDeleted)
{
[self.managedObjectContext deleteObject:currentEmployee];
}
}

Firstly, in your deleteAllEmployeesForDepartment: this is fine to remove objects that way.
If in addition you want to delete the Employees objects from Core Data then you should add another rule, a delete rule set to cascade, meaning that when an Employee "losses" a Department (the relationship is broken, either by the Department being deleted, or the Department removing the Employee), it (the Employee) also is deleted.
Your second question is a little more interesting.
What I recommend is adding another method directly to the Department subclass of NSManagedObeject, you could call it - clearEmployeesOfType: passing a type number.
Since your Department has a reference to an NSSet of Employees via the to-many relationship you could use that NSSet and it's filteredSetUsingPredicate: method to filter out the ones you want.
The returning set can be used to message the removeEmployees: method on your Department, a little like the following (warning, code not tested).
- (void) clearEmployeesOfType:(NSUInteger)type
{
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SELF.type == %d", type];
NSSet * firedEmployees = [self.employees filteredSetUsingPredicate:predicate];
[self removeEmployees:firedEmployees];
}
I would recommend this solution rather then loading objects and removing them one by one, whenever you can, rely on relationships and delete rules in Core Data.

Related

Core Data, iCloud and Pre-Built Data with iOS 7

I'm a in a really common situation mostly driven by inexperience...
My application inserts pre-built data in the DB at the first launch.
I just check for db existence in the device... if it doesn't exist this is the first launch so I add my pre-built data after DB creation.
At the end of the development I decide to activate iCloud, wow! just one row of code to make it works... I tried it on different devices and my pre-built data were obviously duplicated :(
I understood that the better way to manage pre-built data is to work with more than one persistent store, creating read-only store for pre-built data (my pre-built data don't need to be modified by the user, so the read-only solution would be ok).
The problem is that I have relationship between data inserted by user so I can't work on multiple persistent store (core-data doesn't support relation between different stores).
I took a look at Fetched Properties but it seems to require a super drastic edit on my code.
I thought to go with de-duplicating data... so just removing data that are already in the DB, my doubt is that this process is really dangerous for relationship, how can I delete the object that is just a duplicate of the "original" created at the first launch?=
Given that my pre-build data are can't be modified or deleted by the users, which is in your opinion the best way to correct this problem?
// EDIT: Some information about the model:
The entity that users are dealing with (add/edit/modify)represents a TASK.
this entities have a name, and a CATEGORY.
CATEGORY are the entities that I pre-build.
So user add a name for the task and select a CATEGORY from the pre-built data.
In your Category entity, in the PrebuiltData model, create an "id" property (and make sure that this is consistent from version to version) This model should not be managed by iCloud.
In your Task entity, in the UserData model, create a "categoryId" property. This model should be managed by iCloud (or this discussion is meaningless)
Now you can create a method on your Category entity to fetch all the Tasks in the category using:
-(NSArray*)tasks
{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Task"];
request.predicate = [NSPredicate predicateWithFormat:#"id = %#", self.id];
return [gTaskManagedObjectContext executeFetchRequest:request error:NULL];
}
Likewise, you can create a method on your Task entity to fetch the category:
-(Category*)category
{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Category"];
request.predicate = [NSPredicate predicateWithFormat:#"id = %#", self.categoryId];
NSArray* results = [gTaskManagedObjectContext executeFetchRequest:request error:NULL];
return results.count > 0 ? results[0] : nil;
}

Many - Many Relationship Core Data

I have a condition where I have many-many for my two entities:
A category can have many places, in other hand, places can be also
under many categories
The data are successfully stored in my sqlite, but when I check the structure of both entites in sqlite manager software, the relationship between two of it is gone.
Usually, when I declare 1..* , for example:
Entity B has one entity A
Place is under one category
Category will become an attribute in Place, where I can use it for later use:
Category *category = (Category*)place.category;
NSString *catId = category.catId;
and it should returns a value of catId if there is value there.
But in my many-many case, I can't even get the catId, it crashes like this:
-[_NSFaultingMutableSet catID]: unrecognized selector sent to instance
Do I miss a concept here? I believe core data supports many to many relationship as I've read it in some web. Thanks!
You have a 'to-many' relationship from Place->Category, so place.category is a NSMutableSet object not a Category object
Try this
NSMutableSet *categorySet = place.category;
foreach (Category *category in categorySet)
{
NSString *catId = category.catId;
NSLog(#"catId: %#", catId);
}

How to perform reodering of cells in One-to-many relationship in CoreData

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.

RestKit, Core Data, and Relationship problems. Parent object not recognizing child set

I am trying to map a relationship between Articles and the Photos that belong to them using RestKit. Both objects get stored properly when requesting the resource, but it seems the relationship does not persist. In fact, the Article model seems to not even respond to the Photos selector (This may be the 'duh' spot, but I will provide full code to be through).
I've provided all code in a gist, as I find it easier to look through and format then on StackOverflow. Sorry if this is actually an inconvenience.
https://gist.github.com/3733334
And here is the image of the core data model and the relationships set up (sorry, I had to combine them since I can only post 2 hyperlinks currently):
http://imageshack.us/a/img33/5039/stackoverflowissue.jpg
Everything seems to be working properlly except the relationship between the objects when I try to access photos via anArticle.photos. The selector is unrecognized. I set up a convience method in the Photo model to return all photos with a matching article ID, but I feel this is an un-ideal solution as it sort of removes the whole idea of creating a relationship.
I feel there may be something simple I am missing and any help would be greatly appreciated.
Thanks!
So of course it was a "Duh" error. After some help from a local developer, he pointed out that my Photos variable in my Article.h file was an NSArray, and needed to be changed to an NSSet to store objects mapped by RestKit.
Theres some inconsistency between different versions of RestKit. If you are using the latest one mappings should be set up as shown here: https://github.com/RestKit/RestKit/wiki/Object-mapping. If you want to use entity classes for model specific methods make categories on your NSManagedObjects so that when you change your data model you can regenerate them (Do this only after you extract your methods to a category! Select an entity in your .xcdatamodeld and go to Editor -> Create NSManagedObject Subclass...).
I moved my mappings to the controller that is responsible for syncing with the remote API.
This shuld be helpful too: http://andriyadi.me/logging-in-restkit/.
Also Core Data guidelines stress that you should set up inverse relations (but it's not obligatory).
Fetching entities can also be done better in my opinion. In my project I have an NSObject subclass singleton that (among some other Core Data convenience functionalities) fetches by entity and predicate:
- (NSArray *)fetchEntities:(NSString *)entity usingPredicate:(NSPredicate *)predicate {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error) {
RLog(#"Error fetching entity %# using predicate %#", entity, predicate);
abort();
}
if ([results count] >= 1) {
return results;
}
return nil;
}
You can alter it to pass predicates as NSStrings too.
Side note:
Currently I'm also struggling with RestKit and object mapping myself :).

How can I save an object that's related to another object in core data?

I'm having difficulty with a one to one relationship. At the highest level, I have a one to many relationship. I'll use the typical manager, employee, example to explain what I'm trying to do. And to take it a step further, I'm trying to add a one to one House relationship to the employe.
I have the employees being added no problem with the addEmployeesToManagereObject method that was created for me when I subclassed NSManagedObject. When I select an Employee in my table view, I set the currentEmployee of type Employee - which is declared in my .h.
Now that I have that current employee I would like to save the Houses entities attributes in relation to the current employee.
The part that I'm really struggling with is setting the managedObjectContext and setting and saving the houses attributes in relation to the currentEmployee.
I've tried several things but here's my last attempt:
NOTE: employeeToHouse is a property of type House that was created for
me when I subclassed NSManagedObject
House *theHouse = [NSEntityDescription
insertNewObjectForEntityForName:#"House"
inManagedObjectContext:self.managedObjectContext];
// This is where I'm lost, trying to set the House
// object through the employeeToHouse relationship
self.currentEmployee.employeeToHouse
How can I access and save the houses attributes to the currentEmployee?
since House is setup as an Entity it can be considered a table within the data store. If that truly is the case, you need to setup a 1 to 1 relationship between Employee and House in your data model.
If you have already done so, then it is as simple as calling. Although I'm not as familiar with one to one relationships with Core Data as I am with to-many. In either case, try one of the following
[self.currentEmployee addHouseObject: theHouse];
or
self.currentEmployee.employeeToHouse=theHouse;
then to the save to the managedObjectContext:
NSError *error=nil;
if (![self.managedObjectContext save:&error]{
NSLog(#"Core Data Save Error: %#", error);
}
Also, I'm not sure about your particular situation, but your self.managedObjectContext should already be the same as the one pointed to by self.currentEmployee.managedObjectContext.
Good luck,
Tim

Resources