Coredata 合并关系而不是覆盖它

Posted

技术标签:

【中文标题】Coredata 合并关系而不是覆盖它【英文标题】:Coredata merges relationship instead of overwriting it 【发布时间】: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),但最终都合并了关系对象而不是覆盖

我在这里遗漏了什么吗?

【问题讨论】:

【参考方案1】:

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

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

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

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

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

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

【讨论】:

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

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

    我创建了自己的自定义 NSMergePolicy override func resolve(constraintConflicts list: [NSConstraintConflict]) throws - 通过重写 resolve 方法,我可以提供我的自定义解析实现。 如果发生任何关系更改,我正在删除带有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 关系值。

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

【讨论】:

以上是关于Coredata 合并关系而不是覆盖它的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 4 CoreData 中设置合并策略

docker compose 覆盖端口属性而不是合并它

React useState 覆盖状态对象而不是合并 [重复]

RestKit / CoreData:两次请求相同的URL时插入重复的对象而不是合并

在扩展/合并核心数据模型时保留数据

如何将一个远程分支覆盖而不是合并到另一个分支?