如何避免更改 NSBatchInsertRequest 中的 属性 值?
How to avoid changing property values in an NSBatchInsertRequest?
我有一个简单的核心数据实体 Story
,我偶尔会使用来自网络调用的最新数据进行更新。这个网络调用有时会更新很多很多故事实例,所以我 运行 一个 NSBatchInsertRequest
,如下所示。 (我使用批量插入的另一个原因是许多故事可能需要添加到持久存储中。)
问题是用户可能已经将 Story
标记为收藏夹。当他们这样做时,我在主线程上设置 story.isFavorite = true
并保存 viewContext
.
但是,当批量插入发生时 它会覆盖 story.isFavorite
,将其设置回 false
,即使我使用的是 NSMergeByPropertyObjectTrumpMergePolicy
在批量插入和查看上下文中。我也没有在批量插入处理程序中触及 story.isFavorite
,所以我不希望 属性 被覆盖。
我认为使用此合并策略进行批量插入的好处是可以避免先获取 + 然后手动更新已更改的属性 + 最后保存。避免更改 NSBatchInsertRequest
中的 属性 值的正确方法是什么?
故事
@objc(Story)
public class Story: NSManagedObject {
@NSManaged public var title: String?
@NSManaged public var storyURL: URL?
@NSManaged public var updatedTime: Date?
@NSManaged public var isFavorite: Bool // <- the problem property
}
批量插入
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = false
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = container.viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.perform {
let batchInsert = NSBatchInsertRequest(entity: Story.entity(), managedObjectHandler: { managedObject in
let story = managedObject as! Story
let storyResponse = downloadedStories[I]
// Update story with latest response data BUT don't modify story.isFavorite.
story.title = storyResponse.title
story.storyURL = storyResponse.storyURL
story.updatedTime = storyResponse.updatedTime
// ...
})
let result = try context.execute(batchInsert) as? NSBatchInsertResult
if let insertedIDs = result?.result as? [NSManagedObjectID] {
// Merge changes into parent context. Skip save() because not needed for batch insert.
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [NSInsertedObjectsKey: insertedIDs], into: [container.viewContext])
}
}
编辑
Story
实体确实具有使用属性 storyURL
的唯一值约束。
Michael Tsai 回答后更新
通过使 Story
实体属性 isFavorite
成为 non-Optional 布尔值 而没有 默认值(虽然之前它被标记为可选我不确定它在这里有什么不同)并保持 Use Scalar Type
框被选中,我可以确认商店中现有的 objects 不会(根本)使用此批处理配置进行修改插入上下文。
context.persistentStoreCoordinator = container.persistentStoreCoordinator
// HOWEVER, observe that regardless of the merge policy below,
// setting `context.parent = container.viewContext` will also
// overwrite the store data!
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
// NSMergeByPropertyObjectTrumpMergePolicy ignores objects in the store
// (which have the same unique constraint value, here equal `storyURL`)
// and overwrites all properties.
// To confirm that the batch insert operation does not modify
// existing Story instances (at all), first delete all instances where
// where isFavorite == false. Then load the all story data again and
// execute the NSBatchInsertRequest with this change to managedObjectHandler:
story.title = storyResponse.title + " (modified)"
您会看到丢失的故事被重新插入,这次它们的标题带有后缀 " (modified)"
;但以前喜欢的故事
不要修改(基本上,使用此设置,批量插入不会 re-insert objects)。
因此 isFavorite
属性 不会被覆盖,但 也不会更改任何应更改的属性 (因为他们收到了新标题,例如).
因此,如果您不希望更新 object,但希望插入全新的 object,则可以使用此方法。
但是,如果您希望 object 需要更新,这里有一些替代方案:
- 您可以选择 运行 一个单独的更新操作,也许
NSBatchUpdateRequest
在您 运行 以这种方式批量插入之后,
- 或者在批量插入之后,您可以在(可能 background/child)上下文中的一个简单循环中更新某些属性,而无需批量操作,如果没有大量数据,这可能没问题;
- 最后,您可以先将新数据批量插入临时存储,然后再以某种方式手动将您选择的属性与新存储合并,然后删除临时存储。
- 一种更简单的方法:您可以在执行批量插入之前获取要保持不变的 all 属性(将它们存储在由 object 键入的字典中的唯一性约束值),然后在批量插入期间再次设置 属性。
- 对于这种方法,您需要使用不同的合并策略,例如
NSMergeByPropertyObjectTrumpMergePolicy
,以便更新后的 object re-insert 进入存储(确保获取所有您不想在批量插入之前丢失的属性)
- 随机想法:How to Save Data When Using One ManagedObjectContext and PersistentStoreCoordinator with Two Stores
我认为实际上不可能通过批量插入请求进行部分更新。很难确定,因为我认为除了 WWDC 会议之外,没有任何这些记录。第一次看2019 session的时候很激动,因为主持人说:
Attributes that are optional or configured with default values can be omitted from the dictionary as well.
In the case of updating an object with unique constraint, the existing values will not be changed.
我的意思是:
- 您可以省略新 object 的值,您将获得默认值或
NULL
。有道理。
- 如果存在 object 而您省略了一个值,则该值不会更改。因此,您可以故意省略值以进行部分更新,即更新其他值,同时让
isFavorite
保持不变。
但是,在编写代码对此进行测试并查看 com.apple.CoreData.SQLDebug
的输出后,NSMergeByPropertyObjectTrumpMergePolicy
实际发生的情况似乎是:
- 如果您省略了必需的值,则会出现验证错误。
- 如果您省略一个可选值,它会将行更新为
NULL
。对于 Swift 中的 Bool
属性,这将变为 false
。
- 如果您省略具有默认值的值,它会将行更新为默认值。
这很遗憾,因为看起来部分更新 可以 通过让 ON CONFLICT
子句只为您实际需要的属性指定 DO UPDATE SET
来实现放。但是(从 macOS 11 开始)Core Data 似乎总是生成 SQL 来设置所有列。
总而言之,对于批量插入,NSMergeByPropertyObjectTrumpMergePolicy
实际上不会根据更改的内容 属性 进行合并(就像常规的 Core Data 保存一样)。相反,它要么插入一个新行(如果 object 不存在),要么覆盖所有列但保留 objectID
(如果 object 存在)。
NSMergeByPropertyStoreTrumpMergePolicy
也不会被 属性 合并。它只是意味着如果存储的 object 已经存在,则单独保留它。
更新 (2021-06-24):我从 DTS 那里听说,Apple 认为上述当前 (iOS 14/macOS 11) 行为是一个错误,应该 让你批量插入而不改变省略的属性。雷达号是79747419.
我有一个简单的核心数据实体 Story
,我偶尔会使用来自网络调用的最新数据进行更新。这个网络调用有时会更新很多很多故事实例,所以我 运行 一个 NSBatchInsertRequest
,如下所示。 (我使用批量插入的另一个原因是许多故事可能需要添加到持久存储中。)
问题是用户可能已经将 Story
标记为收藏夹。当他们这样做时,我在主线程上设置 story.isFavorite = true
并保存 viewContext
.
但是,当批量插入发生时 它会覆盖 story.isFavorite
,将其设置回 false
,即使我使用的是 NSMergeByPropertyObjectTrumpMergePolicy
在批量插入和查看上下文中。我也没有在批量插入处理程序中触及 story.isFavorite
,所以我不希望 属性 被覆盖。
我认为使用此合并策略进行批量插入的好处是可以避免先获取 + 然后手动更新已更改的属性 + 最后保存。避免更改 NSBatchInsertRequest
中的 属性 值的正确方法是什么?
故事
@objc(Story)
public class Story: NSManagedObject {
@NSManaged public var title: String?
@NSManaged public var storyURL: URL?
@NSManaged public var updatedTime: Date?
@NSManaged public var isFavorite: Bool // <- the problem property
}
批量插入
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = false
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = container.viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.perform {
let batchInsert = NSBatchInsertRequest(entity: Story.entity(), managedObjectHandler: { managedObject in
let story = managedObject as! Story
let storyResponse = downloadedStories[I]
// Update story with latest response data BUT don't modify story.isFavorite.
story.title = storyResponse.title
story.storyURL = storyResponse.storyURL
story.updatedTime = storyResponse.updatedTime
// ...
})
let result = try context.execute(batchInsert) as? NSBatchInsertResult
if let insertedIDs = result?.result as? [NSManagedObjectID] {
// Merge changes into parent context. Skip save() because not needed for batch insert.
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [NSInsertedObjectsKey: insertedIDs], into: [container.viewContext])
}
}
编辑
Story
实体确实具有使用属性 storyURL
的唯一值约束。
Michael Tsai 回答后更新
通过使 Story
实体属性 isFavorite
成为 non-Optional 布尔值 而没有 默认值(虽然之前它被标记为可选我不确定它在这里有什么不同)并保持 Use Scalar Type
框被选中,我可以确认商店中现有的 objects 不会(根本)使用此批处理配置进行修改插入上下文。
context.persistentStoreCoordinator = container.persistentStoreCoordinator
// HOWEVER, observe that regardless of the merge policy below,
// setting `context.parent = container.viewContext` will also
// overwrite the store data!
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
// NSMergeByPropertyObjectTrumpMergePolicy ignores objects in the store
// (which have the same unique constraint value, here equal `storyURL`)
// and overwrites all properties.
// To confirm that the batch insert operation does not modify
// existing Story instances (at all), first delete all instances where
// where isFavorite == false. Then load the all story data again and
// execute the NSBatchInsertRequest with this change to managedObjectHandler:
story.title = storyResponse.title + " (modified)"
您会看到丢失的故事被重新插入,这次它们的标题带有后缀 " (modified)"
;但以前喜欢的故事
不要修改(基本上,使用此设置,批量插入不会 re-insert objects)。
因此 isFavorite
属性 不会被覆盖,但 也不会更改任何应更改的属性 (因为他们收到了新标题,例如).
因此,如果您不希望更新 object,但希望插入全新的 object,则可以使用此方法。
但是,如果您希望 object 需要更新,这里有一些替代方案:
- 您可以选择 运行 一个单独的更新操作,也许
NSBatchUpdateRequest
在您 运行 以这种方式批量插入之后, - 或者在批量插入之后,您可以在(可能 background/child)上下文中的一个简单循环中更新某些属性,而无需批量操作,如果没有大量数据,这可能没问题;
- 最后,您可以先将新数据批量插入临时存储,然后再以某种方式手动将您选择的属性与新存储合并,然后删除临时存储。
- 一种更简单的方法:您可以在执行批量插入之前获取要保持不变的 all 属性(将它们存储在由 object 键入的字典中的唯一性约束值),然后在批量插入期间再次设置 属性。
- 对于这种方法,您需要使用不同的合并策略,例如
NSMergeByPropertyObjectTrumpMergePolicy
,以便更新后的 object re-insert 进入存储(确保获取所有您不想在批量插入之前丢失的属性)
- 对于这种方法,您需要使用不同的合并策略,例如
- 随机想法:How to Save Data When Using One ManagedObjectContext and PersistentStoreCoordinator with Two Stores
我认为实际上不可能通过批量插入请求进行部分更新。很难确定,因为我认为除了 WWDC 会议之外,没有任何这些记录。第一次看2019 session的时候很激动,因为主持人说:
Attributes that are optional or configured with default values can be omitted from the dictionary as well. In the case of updating an object with unique constraint, the existing values will not be changed.
我的意思是:
- 您可以省略新 object 的值,您将获得默认值或
NULL
。有道理。 - 如果存在 object 而您省略了一个值,则该值不会更改。因此,您可以故意省略值以进行部分更新,即更新其他值,同时让
isFavorite
保持不变。
但是,在编写代码对此进行测试并查看 com.apple.CoreData.SQLDebug
的输出后,NSMergeByPropertyObjectTrumpMergePolicy
实际发生的情况似乎是:
- 如果您省略了必需的值,则会出现验证错误。
- 如果您省略一个可选值,它会将行更新为
NULL
。对于 Swift 中的Bool
属性,这将变为false
。 - 如果您省略具有默认值的值,它会将行更新为默认值。
这很遗憾,因为看起来部分更新 可以 通过让 ON CONFLICT
子句只为您实际需要的属性指定 DO UPDATE SET
来实现放。但是(从 macOS 11 开始)Core Data 似乎总是生成 SQL 来设置所有列。
总而言之,对于批量插入,NSMergeByPropertyObjectTrumpMergePolicy
实际上不会根据更改的内容 属性 进行合并(就像常规的 Core Data 保存一样)。相反,它要么插入一个新行(如果 object 不存在),要么覆盖所有列但保留 objectID
(如果 object 存在)。
NSMergeByPropertyStoreTrumpMergePolicy
也不会被 属性 合并。它只是意味着如果存储的 object 已经存在,则单独保留它。
更新 (2021-06-24):我从 DTS 那里听说,Apple 认为上述当前 (iOS 14/macOS 11) 行为是一个错误,应该 让你批量插入而不改变省略的属性。雷达号是79747419.