如何防止 NSManagedObject 在覆盖合并时与 CoreData 中的 Swift 重复?

How to prevent to-many relationship NSManagedObject duplicates on overwrite merge with Swift in CoreData?

我的Swift应用程序以JSON的形式从网上API下载了3个数据库,然后将JSON对象转换为CoreData对象,所以应用程序可以运行在互联网访问之外。

我有一个实体 Client,它与 Address 类型的实体具有 toMany 关系。地址与实体客户端具有一对一关系。

客户 <-->> 地址

Client to Addresses 关系具有级联删除规则,Address to Client 关系具有无效删除规则。

Client对id属性有唯一性约束,context始终使用NSMergePolicyType.overwriteMergePolicyType.

当一个新的客户端 NSManagedObject 被实例化,上下文被保存,并且找到一个具有相同 ID 的客户端时,新的客户端覆盖旧的,有一个重要的警告 - 由于某种未知的原因,旧的 Address 对象仍然存在,现在链接到新客户端。这会导致每次重新加载 cache/database 时每个地址都有一个新副本。

我有多个实体具有这样的关系和唯一性,它们 运行 到相同的结果 - 对多对象实例的副本。

对于像 Address 这样的对象,没有一个属性可以封装容器中所有其他 Address 对象的唯一性。它必须是所有属性(地址 1、地址 2、城市、州、邮政编码等)的总和,并根据另一个地址的所有属性的总和检查其唯一性。所以我不确定如何通过唯一性约束来实现这一点——据我所知,唯一性约束不能用 if 逻辑扩展。

另一种解决方案是更改策略的合并行为,或创建自定义合并策略,以确保它在替换之前实际删除旧对象(级联到多对多关系对象)新对象。

我对 CoreData 或 objective-c 不够熟悉,无法了解到目前为止我能找到的关于该主题的任何内容。

有没有人对如何 A. 扩展唯一性约束功能,B. 定义合并策略行为,或 C. 以其他方式防止上述地址对象重复提出建议?

编辑:

我怀疑我关于唯一性约束的假设是错误的 - 请参阅我的单独问题 here

好吧,我可以向你保证,无论是 Objective-C 的知识,还是阅读 Apple 关于子类化 NSMergePolicy 的 non-existent 文档,都不会帮助你解决这个问题:)

我在我自己的小演示项目中确认 Core Data 的唯一性约束不会像人们对 Core Data 的级联删除规则所期望的那样发挥作用。正如您报告的那样,在您的情况下,您只是不断获得越来越多的地址对象。

以下代码解决了我的演示项目中地址对象重复的问题。然而,它的复杂性让人想知道放弃 Core Data 的唯一性约束并编写您自己的老式唯一性代码是否会更好。我想这可能会表现更差,但你永远不知道。

当de-duplicating寻址对象时,可以将现有对象保留在持久存储中或创建新对象。如果确实所有属性都相等,那应该无关紧要。以下代码保留现有对象。这具有不增加对象标识符字符串表示中的 "p" 后缀的美观效果。它们仍然是 "p1"、"p2"、"p3" 等

当您创建持久容器时,在 loadPersistentStores() 完成处理程序中,您将自定义合并策略分配给托管对象上下文,如下所示:

container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    container.viewContext.mergePolicy = MyMergePolicy(merge: .overwriteMergePolicyType)
    ...
})

最后,这是您的自定义合并策略。传递给 resolve(constraintConflicts list:) 的合并冲突中的 Client 对象具有新的 Address 对象。覆盖删除这些,然后调用核心数据的标准合并策略之一,根据需要附加现有的地址对象。

class MyMergePolicy : NSMergePolicy {
    override func resolve(constraintConflicts list: [NSConstraintConflict]) throws {
        for conflict in list {
            for object in conflict.conflictingObjects {
                if let client = object as? Client {
                    if let addresses = client.addresses {
                        for object in addresses {
                            if let address = object as? Address {
                                client.managedObjectContext?.delete(address)
                            }
                        }
                    }
                }
            }
        }

        /* This is kind of like invoking super, except instead of super
          we invoke a singleton in the CoreData framework.  Weird. */
        try NSOverwriteMergePolicy.resolve(constraintConflicts: list)

        /* This section is for development verification only.  Do not ship. */
        for conflict in list {
            for object in conflict.conflictingObjects {
                if let client = object as? Client {
                    print("Final addresses in Client \(client.identifier) \(client.objectID)")
                    if let addresses = client.addresses {
                        for object in addresses {
                            if let address = object as? Address {
                                print("   Address: \(address.city ?? "nil city") \(address.objectID)")
                            }
                        }
                    }
                }
            }
        }

    }
}

请注意,此代码建立在覆盖合并策略的基础上。我不确定特朗普的其中一项政策是否更合适。

我很确定这就是您所需要的。如果我遗漏了什么,请告诉我。

不确定这是否相关,但我不得不处理类似的情况。我也有关联的对象,它们似乎没有任何我想覆盖的独特之处,但如果你考虑一下 - 它们确实有一些独特之处:事实上它们与特定的独特对象相关方法。 所以我这样做的方法是我连接了足够的关于我的 "Address" 对象的数据,并附加了我的 "Client" 的唯一键,这是我的 "Address".[=10 的唯一键=]