【问题标题】:Coredata merges relationship instead of overwriting itCoredata 合并关系而不是覆盖它
【发布时间】:2019-10-23 18:59:58
【问题描述】:

我有两个 coredata 实体,例如具有一对多关系的 Class 和 Student(级联删除,有序)。我在实体 customId 上启用了唯一约束。 customId 不是可选的 当我用学生 A 和 B 创建一个新的 ClassX 对象时,它工作正常。

我更新此对象(类)的方式是创建一个具有相同“customId”的新对象,该对象定义了所有关系。例如,Now ClassX 有学生 C 和 D。 当我保存上下文时,它会导致一个包含学生 A、B、C 和 D 的最终对象 ClassX。 这里的期望是覆盖所有关系,并且 X 类应该只有 C 和 D 作为学生。

我尝试了不同的合并策略(NSMergeByPropertyObjectTrumpMergePolicy 和 NSOverwriteMergePolicy),但最终都合并了关系对象而不是覆盖

我在这里遗漏了什么吗?

【问题讨论】:

    标签: ios core-data


    【解决方案1】:

    Core Data 默认为NSSet 类型关系的“对多”端创建与对象的一对多关系。

    NSSet 代表一个唯一的对象集合。 Apple Documentation.

    当您在对象模型编辑器中选中“Ordered”复选框时,Core Data 将提供 NSOrderedSet 类型的“to-many”对象,而不是 NSSet。 Apple Documentation。

    一个集合本质上是如上所述的,一个独特的集合。有关这方面的更多信息,有一些关于收藏的好博客,我建议值得一读。如果我找到任何链接,我会在这里发布。

    因此,当您将对象添加到“对多”关系时,Xcode 和 Core Data 框架准备的样板代码会将对象添加到集合中。

    如果您想“覆盖”...您需要先删除现有对象,然后再添加新对象。

    【讨论】:

    • 要删除现有对象,我需要从持久存储中获取对象,这有点违背了在 coredata 中唯一化的目的。
    【解决方案2】:

    我遇到了同样的情况,以下解决方案通过覆盖关系帮助我解决了关系。

    1. 我创建了自己的自定义 NSMergePolicy
    2. override func resolve(constraintConflicts list: [NSConstraintConflict]) throws - 通过重写 resolve 方法,我可以提供我的自定义解析实现。
    3. 如果发生任何关系更改,我正在删除带有storeObject.linkedContacts?.forEach { storeObject.managedObjectContext?.delete($0) } 的商店关系数据。这将有助于覆盖我们更新的数据。
    class CustomMergePolicy: NSMergePolicy {
        
            class func create() -> MergePolicy {
                return MergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
            }
            
            private override init(merge ty: NSMergePolicyType) {
                super.init(merge: ty)
            }
        
            /* 
             NOTE: Things to consider
             1. Even if we edit single property in the json -> all keys coming as changedKeys
             2. if json property become null or empty -> its not coming as changedKeys when resolving conflicts
             */
            override func resolve(constraintConflicts list: [NSConstraintConflict]) throws {
                for conflict in list {
                    guard let storeObject = conflict.databaseObject else {
                        try super.resolve(constraintConflicts: list)
                        return
                    }
        
                    var allKeys = Array(storeObject.entity.attributesByName.keys)
                    allKeys.append(contentsOf: storeObject.entity.relationshipsByName.keys)
        
                    for conflicting in conflict.conflictingObjects {
                        let changedKeys = conflicting.changedValues().keys
                        let unchangedKeys = allKeys.filter { !changedKeys.contains($0) }
        
                        func takeValueFromStoreForUnchangedKeys() {
                            for key in unchangedKeys {
                                let value = storeObject.value(forKey: key)
                                conflicting.setValue(value, forKey: key)
                            }
                        }
                        switch conflicting {
                        case conflicting as? Item:
                            guard let storeObject = storeObject as? Item else { fallthrough }
                            let hasLinkedContactChanges = changedKeys.contains("linkedContacts")
                            let isLinkedContactNullified = !storeObject.linkedContacts.isNilOrEmpty && !hasLinkedContactChanges // #2
                            let hasChanges = hasLinkedContactChanges || isLinkedContactNullified
                            func deleteStoreLinkedContacts() {
                                storeObject.linkedContacts?.forEach { storeObject.managedObjectContext?.delete($0) }
                            }
                            hasChanges
                            ? deleteStoreLinkedContacts()
                            : takeValueFromStoreForUnchangedKeys() // this never executed - need to find scenario
                        default:
                            takeValueFromStoreForUnchangedKeys()
                        }
                    }
                }
        
                try super.resolve(constraintConflicts: list)
            }
        }    
    

    我已经使用默认和更新的 json 进行了测试。它通过丢弃存储关系数据来获取更新的 json 关系值。

    有点延迟回答,希望这有助于其他人解决问题。

    【讨论】:

      猜你喜欢
      • 2012-11-03
      • 2018-07-28
      • 2012-10-24
      • 2020-04-21
      • 2019-05-09
      • 1970-01-01
      • 1970-01-01
      • 2011-11-24
      • 1970-01-01
      相关资源
      最近更新 更多