How do you manage and use "Many to many" core data relationships?

16,752

Solution 1

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.

Solution 2

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

Solution 3

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

Solution 4

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.

Share:
16,752

Related videos on Youtube

Darkenor
Author by

Darkenor

Software Developer by day, nerd by always.

Updated on June 22, 2022

Comments

  • Darkenor
    Darkenor almost 2 years

    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:

    http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW10

    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?

    • Scott Harwell
      Scott Harwell over 12 years
      Is this a duplicate of stackoverflow.com/questions/1449523/… ?
    • Darkenor
      Darkenor over 12 years
      No, because that guy reference a join table. From what I've read in the docs, you don't need to create a join table? Further, I think he's asking about fetching data.
  • Darkenor
    Darkenor over 12 years
    But, eh - how do I see which employees have managers and which managers have employees? I mean, they'd just be a large list otherwise?
  • Darkenor
    Darkenor over 12 years
    Well, knowing how it works is good, but what I really want to know is how to access the crossed reference table? I mean, how else are you supposed to actually map the two entities to one another? An attribute from one entity doesn't have every attribute of the other entity.
  • Tim Dean
    Tim Dean over 12 years
    I updated the response a bit to include an example of accessing the cross referenced data using code. If you're looking for an example of a specific kind of query, please clarify what your looking for and we could possibly provide a predicate you can use to achieve what you want.
  • SinisterMJ
    SinisterMJ over 12 years
    You would say employee.managers to get a set of managers for the employee. From a manager you would simply say manager.employees and get a set of all the employee entities for that manager. ("employee" and "manager" are both specific instances of an entity that refer to one person).
  • Darkenor
    Darkenor over 12 years
    That's one of the best answers I have ever seen on stack overflow. Thank you so much! I practically have tears in my eyes I was so confused on this.
  • raw3d
    raw3d over 9 years
    how can i get all employees in the context before the context is being saved ?
  • morningstar
    morningstar over 9 years
    It doesn't matter if the context is saved. The result of accessing the employees in code is the same whether the context is saved or not. Whether it's saved only affects what happens if you stop and start the program, or discard the context, etc.
  • Lydia
    Lydia about 8 years
    I want to display 4 column from one entity 2 column from another entity,so how can i do that?
  • NSPratik
    NSPratik about 8 years
    Suppose there are 2 entities having many-to-many relationship (A and B). Suppose I add B1 and B2 for A1 record, B1 and B3 for A2 record. For B1, will I automatically get (A1 and A2) ?
  • SinisterMJ
    SinisterMJ over 5 years
    @MohsinKhubaibAhmed - Yep, Mogenerator still works and I've been on projects very recently that use it for core data object generation.
  • Mohsin Khubaib Ahmed
    Mohsin Khubaib Ahmed over 5 years
    nice, i'll check it out in detail.. can you recommend any resource to get me going on this? as in picturesque presentation or something
  • Mohsin Khubaib Ahmed
    Mohsin Khubaib Ahmed over 5 years
    Nevermind, i found it: medium.com/@yzhong.cs/…