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 合并关系而不是覆盖它的主要内容,如果未能解决你的问题,请参考以下文章
React useState 覆盖状态对象而不是合并 [重复]