swiftcore-databatch-insert

Error - objects in different contexts when setting relationship during a batch insert


I have a function used to create an object graph in my app for testing purposes. The data structure is very simple at present with a one-to-many relationship between Patient and ParameterMeasurement entities.

As setup of the test state involves around 800 entries it makes sense to do this as a batch insert which works...until you try and establish the relationship between ParameterMeasurement and Patient (which, in the reciprocal, is a one-to-one) at which point the app crashes with the dreaded "Illegal attempt to establish a relationship 'cdPatient' between objects in different contexts"

I'm struggling to understand why this is happening as both Patient and ParameterMeasurement entities are created using the same managed object context which is passed to the function by the caller.

I've already tried to store the objectID of the Patient (created before instantiating ParameterMeasurement instances) and then creating a local copy of the Patient instance inside the batch insert closure (code in place below but commented out) but this does not resolve the issue. I've also checked my model (all OK, relationships are good), deleted the app and reset the sim but still no joy.

Finally, I've stuck in print statements to check the MOCs associated with both entities at the point of instantiation and the MOC passed to the function. As expected, the memory addresses match which makes it look like the error message is a red herring.

Can anyone point me in the right direction? This seems to have been a common issue in the past (lots of posts 5y+ ago with ObjC but little in Swift) but the examples on don't deal with this specific scenario.

func addSampleData(to context: NSManagedObjectContext) throws {
    try addParameterDefinitions(to: context, resetToDefaults: true)

    let fetchRequest = ParameterProfile.fetchAll
    let profiles = try context.fetch(fetchRequest)

    for _ in 1...10 {
        let patient = Patient(context: context)
        patient.cdName = "Patient \(UUID().uuidString.split(separator: "-")[0])"
        patient.cdCreationDate = Date()

        //            let patientID = patient.objectID

        for profile in profiles {
            let data: [(Date, Double)] = DataGenerator.placeholderDataForParameter(with: profile)

            var idx = 0
            let total = data.count

            let batchInsert = NSBatchInsertRequest(entity: ParameterMeasurement.entity()) { (managedObject: NSManagedObject) -> Bool in
                guard idx < total else { return true }

                //                    let patientInContext = context.object(with: patientID) as! Patient
                if let measurement = managedObject as? ParameterMeasurement {
                    //                        measurement.cdPatient = patientInContext
                    measurement.cdPatient = patient
                    measurement.cdName = profile.cdName
                    measurement.cdTimestamp = data[idx].0
                    measurement.cdValue = data[idx].1
                }

                idx += 1
                return false
            }

            do {
                try context.execute(batchInsert)
                try context.save()
            } catch {
                fatalError("Import failed with error: \(error.localizedDescription)")
            }
        }
    }
}

Core Data model definitions: Patient NSMO subclass

ParameterMeasurement NSMO subclass


Solution

  • Having done some more digging on this, it appears that batch inserts cannot be used to add relationships to the persistent store as noted here. I'm guessing its because of the difficulties associated with correctly associating entities during the process - frustrating but not a deal breaker.

    For now, I'll revert to individual insertion of entities although I could do the process in 2 passes, i.e. a batch insert of the "basic" properties and a second pass setting the relationships on the inserted entities. It seems like a bit too much effort at this level though and any time saving is likely to be minimal for the extra code complexity (and risk of bugs!)