Search code examples
core-datamany-to-manyrelational-databaseone-to-manyrelation

CoreData to-many relationship: "addXxxxlist" - how to replace the list completely


I have a Person entity which has Contacts to-many relationship to an entity name Contact.

CoreData creates a NSSet property for me:

@property (nullable, nonatomic, retain) NSSet<Contact *> *contact_list;

What if I need to completely change the person's contact list? Can I just replace the whole set:

person.contact_list = newContactList

Or should I actually always use CoreData-generated accessors and mutators? e.g.:

person.removeContact_list(person.contact_list) // removing all the current contacts
person.addContact_list(newContactList) // setting a new contact list

Could the former approach cause database errors in the relationship configuration? What is preferred way to completely replace a to-many relationship set?


Solution

  • I assume the core data model is like this:

    enter image description here

    Question 1: Can I just replace the whole set: person.contact_list = newContactList or should I actually always use CoreData-generated accessors and mutators?

    Answer: If all data objects are formerly saved in the database, Core Data creates the same SQL update code (i'm using Swift here but with ObjC it is only syntax differences):

    Prepare some objects and save it:

        // Create Persons
        let personObjA: Person = Person(context: mainContext)
        personObjA.name = "Person A"
        let personObjB: Person = Person(context: mainContext)
        personObjB.name = "Person B"
        saveContext()
        // Create Contacts
        let contactObj1: Contact = Contact(context: mainContext)
        contactObj1.name = "Contact A"
        let contactObj2: Contact = Contact(context: mainContext)
        contactObj2.name = "Contact B"
        let contactObj3: Contact = Contact(context: mainContext)
        contactObj2.name = "Contact C"
        let contactObj4: Contact = Contact(context: mainContext)
        contactObj2.name = "Contact D"
        saveContext()
    

    Method by assign to a new contact list:

        // Set Contacts for PersonA
        personObjA.addToContacts([contactObj1,contactObj2])
        saveContext()
        // Replace Contacts for PersonA
        personObjA.contacts = [contactObj3,contactObj4]
        saveContext()
    

    Core Data executes this SQL:

    CoreData: sql: BEGIN EXCLUSIVE
    CoreData: sql: UPDATE OR FAIL ZPERSON SET Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
    CoreData: details: SQLite bind[0] = (int64)3
    CoreData: details: SQLite bind[1] = (int64)1
    CoreData: details: SQLite bind[2] = (int64)2
    CoreData: sql: UPDATE OR FAIL ZCONTACT SET Z2CONTACTS = ?, Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
    CoreData: details: SQLite bind[0] = (int64)1
    CoreData: details: SQLite bind[1] = (int64)2
    CoreData: details: SQLite bind[2] = (int64)2
    CoreData: details: SQLite bind[3] = (int64)1
    CoreData: sql: UPDATE OR FAIL ZCONTACT SET Z2CONTACTS = ?, Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
    CoreData: details: SQLite bind[0] = (int64)1
    CoreData: details: SQLite bind[1] = (int64)2
    CoreData: details: SQLite bind[2] = (int64)3
    CoreData: details: SQLite bind[3] = (int64)1
    CoreData: sql: COMMIT
    

    Method by using Core Data accessors and mutators:

        // Set Contacts for PersonB
        personObjB.addToContacts([contactObj1,contactObj2])
        saveContext()
        // Replace Contacts for PersonB
        personObjB.removeFromContacts([contactObj1,contactObj2])
        personObjB.addToContacts([contactObj3,contactObj4])
        saveContext()
    

    Core Data will execute SQL like this:

    CoreData: sql: BEGIN EXCLUSIVE
    CoreData: sql: UPDATE OR FAIL ZPERSON SET Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
    CoreData: details: SQLite bind[0] = (int64)3
    CoreData: details: SQLite bind[1] = (int64)2
    CoreData: details: SQLite bind[2] = (int64)2
    CoreData: sql: UPDATE OR FAIL ZCONTACT SET Z2CONTACTS = ?, Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
    CoreData: details: SQLite bind[0] = (int64)2
    CoreData: details: SQLite bind[1] = (int64)3
    CoreData: details: SQLite bind[2] = (int64)2
    CoreData: details: SQLite bind[3] = (int64)2
    CoreData: sql: UPDATE OR FAIL ZCONTACT SET Z2CONTACTS = ?, Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
    CoreData: details: SQLite bind[0] = (int64)2
    CoreData: details: SQLite bind[1] = (int64)3
    CoreData: details: SQLite bind[2] = (int64)3
    CoreData: details: SQLite bind[3] = (int64)2
    CoreData: sql: COMMIT
    

    Question 2: Could the former approach cause database errors in the relationship configuration?

    Answer: If there are simple updates all done in same thread, same context (...) as described in former answer to Question 1 there will no errors in relationship.

    Question 3: What is preferred way to completely replace a to-many relationship set?

    Answer: If there are simple update steps all done in same thread, same context (...) as described in former answer to Question 1 both is good.