Preserving Core Data relationships in a merge policy

I am trying to merge a Core Data object with a unique constraint while preserving relationships. In particular, I am trying to avoid overwriting a relationship with nil.


The relationship is optional, but the inverse is mandatory. As such, the delete rule is set to “Cascade”, which makes it particularly important that it doesn’t get wiped.


I’m using the following custom merge policy, and it seems to work… at first. Before saving is complete, the following warning gets printed to the console: `CoreData: annotation: repairing missing delete propagation for to-one relationship […] on object […] with bad fault […]`. Core Data seems to deliberately erase anything I try to preserve. What is causing that, and how can I stop it?

private class NSMergeByPropertyNonNilObjectTrumpMergePolicy: NSMergePolicy {
   override func resolve(constraintConflicts list: [NSConstraintConflict]) throws {
      try super.resolve(constraintConflicts:
        list.compactMap { conflict in
          // don't customize context-level handling
          guard
            let databaseSnapshot = conflict.databaseSnapshot,
            let conflictingObject = conflict.conflictingObjects.first else {
              return conflict
          }


          databaseSnapshot
            .filter {
              conflictingObject.value(forKey: $0.key) == nil && !($0.value is NSNull)
          }
          .forEach {
            // to-one relationships
            if let objectID = $0.value as? NSManagedObjectID {
              conflictingObject.setValue(
                conflictingObject.managedObjectContext!.object(with: objectID),
                forKey: $0.key
              )
            } else {
              conflictingObject.setValue($0.value, forKey: $0.key)
            }
          }
          return conflict
        }
      )
    }
  }

I've gone through a bunch of different merge policy approaches, and they all have the same problem.

Replies

I'm using the following merge policy to skip certain attributes by being trumped. I didn't test it with relationships yet, but I don't see why it shouldn't work.

It would be great if you give some feedback here when you test it.



override func resolve(constraintConflicts list: [NSConstraintConflict]) throws {
    guard list.allSatisfy({ $0.databaseObject != nil }) else {
        print("NSMergeByKeepingPropertiesPolicy is only intended to work with database-level conflicts.")
        return try super.resolve(constraintConflicts: list)
    }
   
    for conflict in list {
        for conflictingObject in conflict.conflictingObjects {
            for key in conflictingObject.entity.attributesByName.keys {
                let databaseValue = conflict.databaseObject?.value(forKey: key)
                if key == #keypath(MyNSManagedObject.MyRelationshipIDontWantToBeUpdated) { continue }

                conflictingObject.setValue(databaseValue, forKey: key)
            }
        }
    }

    try super.resolve(constraintConflicts: list)
}