核心数据在一对多关联中双重插入子记录
Core Data double-inserting child records in one-to-many association
我们有一个 iOS 应用程序,它使用 Core Data 来保存从私人网站 API 获取的记录。我们的一个 API 请求获取 Project
记录的列表,每个记录都有多个关联的 Location
记录。 ObjectMapper 用于反序列化 JSON 响应,我们有一个自定义转换器,将嵌套的 Location
属性分配给 Project
实体上的核心数据关联。
代码的相关部分如下所示。它在 PromiseKit promise(因此是 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 数据库进行查询,我可以看到每个 [= 有两个条目=12=],但 project.locations
每个 Location
只有 returns 一份。应用于具有相同结构的其他记录类型的相同(或非常相似)过程也会导致重复。
到目前为止,我已经尝试了几种方法来缩小问题范围:
- 检查了 API JSON - 没有重复。
- 检查
project.locations
属性 核心数据写入前的状态。在持久化对象之前不存在重复记录,表明反序列化器和自定义嵌套属性转换器工作正常。
- 删除了将更改传播到主线程管理对象上下文的块,以防这导致插入发生两次。仅写入后台上下文仍然会重复。
- 运行 设置了
com.apple.CoreData.ConcurrencyDebug 1
的应用程序。这个过程没有抛出异常,确认不是某种线程安全问题。
- 运行 设置了
com.apple.CoreData.SQLDebug 1
的应用程序。我可以在日志中看到,Core Data 向底层 SQLite 数据库中插入的 Location
行正好是预期的两倍。
- 对实体实施了唯一性约束。这解决了持久化数据方面的问题,但除非设置了
NSMergePolicy
,否则仍会抛出错误。
该列表中的最后一项有效地解决了问题,但它是治标不治本。数据完整性对我们的应用程序很重要,我希望了解潜在的问题可能是什么,或者我可能会寻求其他选项来进一步调查它。
谢谢!
一年零八个月后,当不同的记录集发生类似问题时,我终于找到了这个错误的根源。问题是我在每个 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)
是不必要的,事实证明是罪魁祸首。在具有两个级别的一对多关联的类似场景中,这具有将第二级别的记录翻四倍(!)的有点有趣的结果 - 它们的父项已被复制,每个副本随后复制其子项。这就是最终导致错误的原因。
我们有一个 iOS 应用程序,它使用 Core Data 来保存从私人网站 API 获取的记录。我们的一个 API 请求获取 Project
记录的列表,每个记录都有多个关联的 Location
记录。 ObjectMapper 用于反序列化 JSON 响应,我们有一个自定义转换器,将嵌套的 Location
属性分配给 Project
实体上的核心数据关联。
代码的相关部分如下所示。它在 PromiseKit promise(因此是 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 数据库进行查询,我可以看到每个 [= 有两个条目=12=],但 project.locations
每个 Location
只有 returns 一份。应用于具有相同结构的其他记录类型的相同(或非常相似)过程也会导致重复。
到目前为止,我已经尝试了几种方法来缩小问题范围:
- 检查了 API JSON - 没有重复。
- 检查
project.locations
属性 核心数据写入前的状态。在持久化对象之前不存在重复记录,表明反序列化器和自定义嵌套属性转换器工作正常。 - 删除了将更改传播到主线程管理对象上下文的块,以防这导致插入发生两次。仅写入后台上下文仍然会重复。
- 运行 设置了
com.apple.CoreData.ConcurrencyDebug 1
的应用程序。这个过程没有抛出异常,确认不是某种线程安全问题。 - 运行 设置了
com.apple.CoreData.SQLDebug 1
的应用程序。我可以在日志中看到,Core Data 向底层 SQLite 数据库中插入的Location
行正好是预期的两倍。 - 对实体实施了唯一性约束。这解决了持久化数据方面的问题,但除非设置了
NSMergePolicy
,否则仍会抛出错误。
该列表中的最后一项有效地解决了问题,但它是治标不治本。数据完整性对我们的应用程序很重要,我希望了解潜在的问题可能是什么,或者我可能会寻求其他选项来进一步调查它。
谢谢!
一年零八个月后,当不同的记录集发生类似问题时,我终于找到了这个错误的根源。问题是我在每个 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)
是不必要的,事实证明是罪魁祸首。在具有两个级别的一对多关联的类似场景中,这具有将第二级别的记录翻四倍(!)的有点有趣的结果 - 它们的父项已被复制,每个副本随后复制其子项。这就是最终导致错误的原因。