Search code examples
ioscore-datansmanagedobjectcontext

Proper way of deleting an object from relationship in Core Data


I have a simple data model with two entities: Person and Company. A person can be assigned to many companies and a company can be assigned to many persons. I've filled the database with 1 company and 2 persons and assigned all persons to the company. During the process of filling the database, Core Data generate logs presented below:

CoreData: sql: INSERT INTO ZCOMPANY(Z_PK, Z_ENT, Z_OPT, ZNAME) VALUES(?, ?, ?, ?)
CoreData: sql: INSERT OR REPLACE INTO Z_1PERSONS(Z_1COMPANIES, Z_2PERSONS) VALUES (1, 2)
CoreData: sql: INSERT OR REPLACE INTO Z_1PERSONS(Z_1COMPANIES, Z_2PERSONS) VALUES (1, 1)
CoreData: sql: INSERT INTO ZPERSON(Z_PK, Z_ENT, Z_OPT, ZNAME) VALUES(?, ?, ?, ?)
CoreData: sql: INSERT INTO ZPERSON(Z_PK, Z_ENT, Z_OPT, ZNAME) VALUES(?, ?, ?, ?)

Next I close the application, launch it again and delete the company. Core Data perform following operation then:

CoreData: sql: SELECT 0, t0.Z_PK FROM Z_1PERSONS t1 JOIN ZPERSON t0 ON t0.Z_PK = t1.Z_2PERSONS WHERE t1.Z_1COMPANIES = ? 
CoreData: annotation: sql connection fetch time: 0.0009s
CoreData: annotation: total fetch execution time: 0.0018s for 2 rows.
CoreData: annotation: to-many relationship fault "persons" for objectID 0x8d281e0 <x-coredata://0AFFC353-8C34-43AD-8D30-4483FB44BEA4/Company/p1> fulfilled from database.
Got 2 rows
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME FROM ZPERSON t0 WHERE  t0.Z_PK = ? 
CoreData: annotation: sql connection fetch time: 0.0008s
CoreData: annotation: total fetch execution time: 0.0016s for 1 rows.
CoreData: annotation: fault fulfilled from database for : 0x8e19940 <x-coredata://0AFFC353-8C34-43AD-8D30-4483FB44BEA4/Person/p1>
CoreData: sql: SELECT 0, t0.Z_PK FROM Z_1PERSONS t1 JOIN ZCOMPANY t0 ON t0.Z_PK = t1.Z_1COMPANIES WHERE t1.Z_2PERSONS = ? 
CoreData: annotation: sql connection fetch time: 0.0009s
CoreData: annotation: total fetch execution time: 0.0022s for 1 rows.
CoreData: annotation: to-many relationship fault "companies" for objectID 0x8e19940 <x-coredata://0AFFC353-8C34-43AD-8D30-4483FB44BEA4/Person/p1> fulfilled from database.
Got 1 rows
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME FROM ZPERSON t0 WHERE  t0.Z_PK = ? 
CoreData: annotation: sql connection fetch time: 0.0006s
CoreData: annotation: total fetch execution time: 0.0014s for 1 rows.
CoreData: annotation: fault fulfilled from database for : 0x8e18f90 <x-coredata://0AFFC353-8C34-43AD-8D30-4483FB44BEA4/Person/p2>
CoreData: sql: SELECT 0, t0.Z_PK FROM Z_1PERSONS t1 JOIN ZCOMPANY t0 ON t0.Z_PK = t1.Z_1COMPANIES WHERE t1.Z_2PERSONS = ? 
CoreData: annotation: sql connection fetch time: 0.0006s
CoreData: annotation: total fetch execution time: 0.0015s for 1 rows.
CoreData: annotation: to-many relationship fault "companies" for objectID 0x8e18f90 <x-coredata://0AFFC353-8C34-43AD-8D30-4483FB44BEA4/Person/p2> fulfilled from database.
Got 1 rows
CoreData: sql: BEGIN EXCLUSIVE
CoreData: sql: DELETE FROM ZCOMPANY WHERE Z_PK = ? AND Z_OPT = ?
CoreData: sql: DELETE FROM Z_1PERSONS WHERE Z_1COMPANIES = 1
CoreData: sql: UPDATE ZPERSON SET Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
CoreData: sql: UPDATE ZPERSON SET Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
CoreData: sql: COMMIT

As we see Core Data fetch from the database all persons assigned to deleted company. Next it fetch a company collection for every person. Eventually expected delete operations are executed on the database:

DELETE FROM ZCOMPANY WHERE Z_PK = ? AND Z_OPT = ?
DELETE FROM Z_1PERSONS WHERE Z_1COMPANIES = 1

When I delete the company, a managedObjectContext is clear (there is no objects registered in it), so there is no reason for clearing invalidate relationship in a memory because there is no objects in the memory. The only things that should be done are database level delete operations listed above.

The situation is a problem because when I have 1000 persons assigned to the company, operation of delete the company take too much time (counted in seconds).

What is the proper way of deleting the company which perform only necessary operation and ensure data consistency?

Here you can download the Xcode project prepared for present the problem: Xcode project


Solution

  • Core Data is not a database. Core Data is an object hierarchy that can persist to a database.

    That is an important distinction in cases like this. When you delete something, Core Data will retrieve objects on both sides of the relationship to make sure that referential integrity is preserved. With SQLite this seems inefficient (and it is). When the store is something else, that double check is required.

    So, how to fix this? One is to acknowledge that the delete is going to take some time and spin up a background, private queue, to handle deletes. Not optimal.

    Another option would be to use a soft reference between those two tables. Again not ideal.

    If you can wait until iOS 8, you can do a batch update to nil out the relationship.