Many - Many Relationship Core Data - ios

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);
}

Related

Avoid repeating code in NSManagedObject categories

In my entity model I have a top-level "Installation" entity, which has a child "cards" relationship. I also have a "Person" entity, which has a child "cards" relationship.
I've written some code which will sort the NSSet of cards to return a specific subset (called sortedCards), and this function can be performed at either the Installation level, or at the Person level.
For exmaple, I want to be able to call:
NSArray *cards = [installation sortedCards];
as well as:
NSArray *cards = [person sortedCards];
Where am I supposed to put this code so that I don't copy the code in two places? I started by putting it in the Installation NSManagedObject category that I created. But if I do that, i need to copy the code into the Person category as well.
Should I put in an NSSet category and call [installation.cards sortedCards] and [person.cards sortedCards]? That doesn't feel right either.
Any help much appreciated.
Duncan
You should place this method in NSSet category, because categories are used for extending basic functionality. And if you need sort NSSet in different places, it should be NSSet category work, not other object or class.

iOS Core data issue

I have made a many-to-many relationship. At first I insert all the data in table 1, the rest of the data isn't available right away.
When the data is available I like to connect it to the right table 1 entries. Should I query table 1 and then set the NSSet with the returned data? Or how would one do this?
To elaborate my question here the example:
[ActivityTable] <<--->> [BannerTable]
At viewDidLoad all the activity are upserted in the ActivityTable. Then the banners from the first activity (first upcoming date) is found from the server.
I got the two (it is always two) banners available but how do I set this?
Used this with help of the answer:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Banner" inManagedObjectContext:context];
Banner *banner = [[Banner alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
banner.image = shieldDictionary[BANNER_IMAGE];
Assuming you are using the generated NSManagedObject subclasses for your entities...
In your CoreData model each relationship has a name.
Your generated entity classes already contain methods to add/remove relationships you defined in the model. So all you have to do is use one of those methods.
Example:
If the tables are called 'ActivityTable' and 'BannerTable', and the relationship in 'ActivityTable' is called 'banners', then the generated methods look like:
- addBannersObject:(BannerTable *)value;
- removeBannersObject:(BannerTable *)value;
- addBanners:(NSSet *)value;
- removeBanners:(NSSet *)value;
Core Data manages a graph of objects, not tables.
The easiest way to realize many-to-many relationships is to use the dynamically-generated accessors like add<Key>Object: and remove<Key>s:.
For instance:
[anActivity addBannerObject:aBanner];
You can use the data model editor to generate class files for your models which declare these generated methods. You can also use -mutableSetValueForKey: to get a proxy that lets you add and remove objects from a relationship. i.e.:
NSMutableSet *banners = [anActivity mutableSetValueForKey:#"banners"];
[banners addObject:aBanner];
It is obviously less verbose to use the generated methods for each relationship, but it achieves the same thing.

Need assistance regarding core data

I am new to core data and trying to create a simple apps using core data. I am currently working on app to save data in to-many relationship, there are several questions and tutorials but I am still confused.
I have two entities person and contactNumbers, I am fetching person and its contact numbers from address book, person A has mobile number, iphone number, home, work, other... which creates to-many relationship.
In my code I didnt subclass any entity, Is it necessary to subclass entities to save data in relationship? I am asking this because I have read that its not necessary to subclass direct working with NSManagedObject class will do the job.
All I want is to save person A first name, last name in person entity and its contact numbers in contactNumbers entity. How to save data in to-many relationship using core data?
Please I request do not refer other stackoverflow questions, raywenderlich tutorial, app code blog tutorials etc.
Please provide a clearly understandable code with concepts to deal this situation. Thanks a lot.
This is my model
if I Do this
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"person" inManagedObjectContext:self.managedObjectContext];
[person setValue:#"first name A" forKey:#"firstName"];
[person setValue:#"last name B" forKey:#"lastName"];
[person setValue:#"123" forKey:#"mobile"];
I got this error
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObject 0x82abcc0> setValue:forUndefinedKey:]: the entity Person is not key value coding-compliant for the key "mobile".
If i do this
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[person setValue:#"first name A" forKey:#"firstName"];
[person setValue:#"last name B" forKey:#"lastName"];
NSManagedObject *contactNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:self.managedObjectContext];
[contactNumber setValue:#"123" forKey:#"home"];
[contactNumber setValue:#"456" forKey:#"iPhone"];
[contactNumber setValue:#"789" forKey:#"mobile"];
[contactNumber setValue:#"111" forKey:#"work"];
[contactNumber setValue:#"112" forKey:#"other"];
its working fine, but how data got related to each other in two entities?
You don't need to subclass.
You just need relations.
Make a relation on Person named contactNumbers that points to the ContactNumber entity, and a matching relation on ContactNumber entity called person that points back to person.
The contactNumber relation should be one to many (because one person has many contacts), while the relation from ContactNumber going back to Person should be one to one (because each contact entry will appear in only one person).
When you get a Person entity then, you'll be able to get a set of ContactNumber objects from the contactNumbers property, and then look through elements in the set. Or you can search ContactNumber entities where the person ID matches the person relation going back to Person.
Followup: Your first code does not work because you cannot reference a single instance through a set.
Your second section of code is fine, just add the line
contactNumber.person = person;
Then you can access the set of contacts from a person at any time with:
NSSet *contacts = person.contactNumbers;
And get the contact number you want. You cannot go directly from a person to one contact without some very tricky keypath stuff.
I would EXTREMELY STRONGLY suggest using Mogenerator to generate data objects after you set up the model, it makes the data clearer as to how you can use it. Using the setKey mechanism with Core Data is UTTERLY INSANE.
You do not need to do any subclassing at all to read and write Core Data relationships (or any other values for that matter). In Core Data, a relationship is represented as an NSMutableSet. Here is some sample code to show you how to retrieve this set and modify it:
// Assuming myPerson is an NSManagedObject instance of the Person entity
NSMutableSet *contactNumbers = [myPerson mutableSetValueForKey:#"contactNumbers"];
// Add numbers normally by calling the set's addObject property
[contactNumbers addObject:myNewNumber];
//etc.
I would also highly recommend taking a look at the Sensible TableView framework if you haven't already done so. The framework will automatically generate the UI for you Core Data entities, including all relationships. It will also automatically handle adding/removing new relationship objects. I myself wouldn't imagine going back to doing stuff manually again without it. Good luck!

Get the Type of a Core Data relationship

Is there an easy way to find the object type which forms a specific relationship in Core Data?
For example, I have a one-to-many relationship:
Battery-----1-to-Many-----Comment
If I didn't know that the relationship was for a specific Comment object, is there a programatic way I could find out which object type it is, based solely on the set that I'm dealing with.
Something along the lines of
battery.comments.classType = [Comment class]
I'm aware that both Battery and Comment are of type NSManagedObject - I'd like to know more specifically what they are.
I'm also aware that if the NSSet contains any data, I can use any one of it's objects to query the type. However I need to cater for when there is no data in the NSSet.
Thank you.
You can get all info you need from this few lines:
NSRelationshipDescription* rel = [[[battery entity] relationshipsByName] valueForKey:#"comments"];
NSString* className = [[rel destinationEntity] managedObjectClassName];
NSString* entityName = [[rel destinationEntity] name];
In your AppDelegate you have an NSManagedObjectModel typed property. It has an entities array containing NSEntityDescriptions. From here you should be able to figure it out. Hope this helps!

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

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.

Resources