Reputation: 4449
I'm creating an app and trying to use core data, because it seems like it's the objective-C approved way to create a data storage system. The use case I have involves "many to many" relationships, like you'd normally see in a standard SQL system. I understand that objective C is not a database and works differently. I've also reviewed the documentation here:
And several other places. And yet, I'm still having trouble. Can someone explain to me what you would do if you have a use case where you'd need to use a SQL cross reference table? For example:
Managers | Employees
Manager may have several employees, but the employees might also have several manager. In SQL, I'd create a cross reference table and then use that.
Example: http://www.tomjewett.com/dbdesign/dbdesign.php?page=manymany.php
Can someone explain how you could do that in Core data?
According to the core data documentation, they say this:
"You define a many-to-many relationship using two to-many relationships. The first to-many relationship goes from the first entity to the second entity. The second to-many relationship goes from the second entity to the first entity. You then set each to be the inverse of the other. (If you have a background in database management and this causes you concern, don't worry: if you use a SQLite store, Core Data automatically creates the intermediate join table for you.)"
But, besides "not being worried," I don't know how this could possibly work?
Upvotes: 27
Views: 18688
Reputation: 9132
Peter: "I have nine bosses, Bob." Bob: "Say again?" Peter: "Nine."
Take your brain out of the database. Do not ask how to access a cross-reference table. Ask how to find the employees for a manager or the managers for an employee.
If you use a custom subclass of NSManagedObject, you can declare the properties as documented. Then you can simply say:
NSSet *mgrsEmployees = mgr.employees;
NSSet *employeesMgrs = employee.managers;
That will not give you all the employees and all the managers, only all the employees for that manager and all the managers for that employee. To change relationships:
[mgr addEmployeesObject:newEmployee];
[newEmployee addManagersObject:mgr]; // not necessary, automatic if you define inverse
[mgr removeEmployeesObject:transferredEmployee];
[transferredEmployee removeManagersObject:mgr]; // not necessary
[newMgr addEmployeesObject:transferredEmployee];
[transferredEmployee addManagersObject:newMgr]; // not necessary
You only need to do either one of each pair of statements, and it will implicitly do the other, provided you have defined managers and employees as inverses of each other.
If you don't use a custom subclass, accessing is a little more verbose
NSSet *mgrsEmployees = [mgr valueForKey:@"employees"];
NSSet *employeesMgrs = [employee valueForKey:@"managers"];
NSMutableSet *changeMgrsEmployees = [mgr mutableSetValueForKey:@"employees"];
[changeMgrsEmployees addObject:newEmployee];
// not necessary
NSMutableSet *changeEmployeesMgrs = [employee mutableSetValueForKey:@"managers"];
[changeEmployeesMgrs addObject:mgr];
Etc.
Upvotes: 66
Reputation: 8163
Let's say you have two entities in your database, employees and managers. They would look something like this in the code:
@interface Employee : NSManagedObject
{
}
@property (nonatomic, retain) NSNumber * id;
@property (nonatomic, retain) NSSet* managers;
@interface Manager : NSManagedObject
{
}
@property (nonatomic, retain) NSNumber * id;
@property (nonatomic, retain) NSSet* employees;
So let's say you wanted to retrieve all the employees for manager number 1, you would just have to get the manager object from the database:
NSNumber *managerID = [NSNumber numberWithInt:1];
NSEntityDescription * entityDescription = [NSEntityDescription entityForName:@"Manager" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %d", @"id", managerID];
[request setPredicate:predicate];
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
Manager *manager = [array objectAtIndex:0];
and then you would have all its employees in the employees property:
NSSet *allHisEmployees = manager.employees;
It would be exactly the same to retrieve the managers for a given employee.
I hope it clears things up
Upvotes: 2
Reputation: 75058
Possibly what you are stuck on is how to get from the abstract data model realm to a set of objects that you use in code. If so, look into using mogenerator:
http://rentzsch.github.com/mogenerator/
That's a command line utility (not sure how well the XCode integration works these days) where you point it at a model, and it gives you sets of data objects you can use. You need to have an object name set for each entity in addition to the entity name.
The generated data objects have the calls like employee.managers, which would give you a set of Manager entities for an employee, and vice-versa.
Upvotes: 0
Reputation: 8292
If your question is "how you could do that in core data", the answer is exactly as you describe. Create a to-many relationship in each entity, and define each as the inverse of the other.
If your question is "how could this possibly work", you might find it useful to look at the sqlite database that is created from your model to see how it sets things up. CoreData is pretty good at making it so you shouldn't care about the details, but if you insist on understanding the details there's nothing stopping you from looking at the data to see what it is doing.
Once you have this in place, you would traverse the relationship as necessary to get the attributes you want. You can do that in code using NSManagedObject instances. For example:
NSManagedObject *manager = // get a reference to a manager here
NSSet *employees = [manager valueForKey:@"employees"];
for (NSManagedObject *employee in employees) {
NSString *employeeName = [employee valueForKey:@"name"];
// Do whatever else you want here
}
This sample can also be simplified using direct property access rather than KVO syntax, but you get the idea.
If you are looking to traverse this many-to-many relationship more directly as part of a fetch query, then you can do some things with predicates to get what you want. Refer to Apple's predicate programming guide for examples of what you can put in a predicate
Upvotes: 4
Reputation: 7465
The simple answer is to mark the relationship as one to many from both ends when setting up core data in xCode. That in essence creates a many to many. It's in the apple docs somewhere, but I can't find the URL.
Upvotes: 2