【问题标题】:Core Data double-inserting child records in one-to-many associationCore Data 在一对多关联中双插入子记录
【发布时间】:2020-07-20 09:01:49
【问题描述】:

我们有一个 iOS 应用程序,它使用 Core Data 来保存从私有 Web API 获取的记录。我们的一个 API 请求获取Project 记录的列表,每个记录都有多个关联的Location 记录。 ObjectMapper 用于反序列化 JSON 响应,我们有一个自定义转换器,它将嵌套的 Location 属性分配给 Project 实体上的核心数据关联。

代码的相关部分如下所示。它在 PromiseKit 承诺中执行(因此是 seal),我们首先保存到后台上下文,然后传播到 UI 线程上使用的主上下文。

WNManagedObjectController.backgroundContext.perform {
    let project = Mapper<Project>().map(JSONObject: JSON(json).object)!

    try! WNManagedObjectController.backgroundContext.save()

    WNManagedObjectController.managedContext.performAndWait {
        do {
            try WNManagedObjectController.managedContext.save()
            seal.fulfill(project.objectID)
        } catch {
            seal.reject(error)
        }
    }
}

我们遇到的问题是这个插入过程将每个Location 记录保存到数据库两次。奇怪的是,重复的 Location 记录与其父 Project 记录没有任何关联。也就是说,如果用NSFetchRequest查找Location记录,或者如果我在底层SQLite数据库上运行查询,我可以看到每个Location有两个条目,但只有project.locations返回每个Location 的一份副本。应用于具有相同结构的其他记录类型的相同(或非常相似)过程也会导致重复。

到目前为止,我已经尝试了几种方法来缩小问题范围:

  • 检查了 API JSON - 没有重复。
  • 在写入核心数据之前检查了project.locations 属性的状态。在对象被持久化之前不存在重复记录,这表明反序列化器和自定义嵌套属性转换器工作正常。
  • 删除了将更改传播到主线程托管对象上下文的块,以防这导致插入发生两次。仅通过写入后台上下文仍然会得到重复项。
  • 设置com.apple.CoreData.ConcurrencyDebug 1 运行应用程序。在这个过程中没有抛出异常,确认它不是某种线程安全问题。
  • 设置com.apple.CoreData.SQLDebug 1 运行应用程序。我可以在日志中看到 Core Data 在底层 SQLite 数据库中插入的 Location 行数正好是预期的两倍。
  • 对实体实施了唯一性约束。这解决了持久化数据方面的问题,但除非设置了NSMergePolicy,否则仍会引发错误。

该列表中的最后一项有效地解决了问题,但它治疗的是症状,而不是原因。数据完整性对我们的应用程序很重要,我希望了解潜在的问题可能是什么,或者我可能会寻求其他选项来进一步调查。

谢谢!

【问题讨论】:

  • 关系是否有逆向关系?
  • @pbasdf 是的,这种关系确实有相反的关系。
  • 我会尝试覆盖 Location 类的 awakeFromInsert,放置一个断点,然后查看回溯是否为您提供任何关于调用插入的代码的线索。
  • @pbasdf 感谢您的建议!我试过这个,awakeFromInsert 确实为每个Location 调用了两次——一次用于后台上下文,来自Location 构造函数,然后当更改传播到父上下文时再次调用。 This question 表示这很正常,所以我不确定重复调用是否有任何意义。
  • 因此,对于每个位置,两次调用 awakefrominsert - 但对于 JSON 中的每个位置,仍然有两个 Location 对象(因此 4 次 awakefrominsert 调用)。回溯是否提供有关导致插入的代码的任何信息?

标签: ios swift core-data objectmapper


【解决方案1】:

一年零八个月后,当另一组记录发生类似问题时,我终于找到了这个错误的根源。问题是我在每个 Location 对象上调用了 ObjectMapper 两次。我在自定义 ObjectMapper TransformType 中使用 ObjectMapper 的 mapArray 方法来反序列化并保留与每个 Project 关联的 Location 记录,其工作原理如下:

let locations = Mapper<Location>().mapArray(JSONObject: value as AnyObject)

但是,我忽略的是,我还重写了 Location 的构造函数并在那里调用 ObjectMapper 再次:

required public init?(map: Map) {
    let entity = NSEntityDescription.entity(forEntityName: "Location", in: WNManagedObjectController.backgroundContext)
    super.init(entity: entity!, insertInto: WNManagedObjectController.backgroundContext)
    mapping(map: map)
}

mapping(map: map) 行是不必要的,并被证明是罪魁祸首。在具有两个级别的一对多关联的类似场景中,这会产生一些有趣的结果,即第二级的记录翻两番(!)——他们的父母被复制了,每个副本随后都复制了它的孩子。这就是最终导致我发现错误的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2021-07-12
    相关资源
    最近更新 更多