我正在实现一个自定义NSIncrementalStore子类,它使用关系数据库进行持久存储。我仍然在努力解决的一件事是对乐观锁定的支持。
(请随意跳过这个冗长的描述,直接回答我下面的问题)
我分析了 Core Data 的 SQLite 增量存储是如何通过检查它产生的 SQL 日志来解决这个问题的,并得出以下结论:
数据库中的每个实体表都有一个Z_OPT列,该列指示该实体(行)的特定实例已被修改的次数,从 1(初始插入)开始。
每次修改托管对象时,其相应数据库行中的Z_OPT值都会增加。
存储维护NSIncrementalStoreNode实例的缓存(在 Core Data 文档中称为行缓存) ,每个实例都有一个版本属性,该值等于先前对托管对象行的SELECT或UPDATE SQL 查询返回的Z_OPT值。
当一个托管对象从NSManagedObjectContext返回时(例如,通过对其执行NSFetchRequest),MOC 创建包含该版本号的该对象的快照。
当对象被修改或删除时,Core Data 通过比较缓存行和对象快照的版本来确保它没有在上下文之外被修改或删除。所有这些都发生在对象所属的上下文中调用-save:时。如果版本不同,则根据设置的合并策略检测和处理合并冲突。
保存 MOC 时,为每个修改/删除的对象调用-newValuesForObjectWithID:withContext:error:方法,该方法又返回带有版本号的NSIncrementalStoreNode 。然后将此版本与快照的版本进行比较,如果它们不同,则保存失败并出现适当的合并冲突(至少使用默认合并策略)。
这个简单的用例适用于我的商店,因为-newValuesForObjectWithID:withContext:error:首先检查行缓存,如果对象在其他上下文中使用相同的商店实例同时修改就足够了。如果是这种情况,则缓存包含具有更高版本号的更新行,足以检测到冲突。
但是,我如何才能检测到底层数据库是否已在我的商店之外被修改,可能是由其他应用程序或其他商店实例使用相同的数据库文件?我知道这是一个不常见的边缘情况,但 Core Data 可以正确处理它,我更愿意这样做。
Core Data 的存储使用这些 SQL 查询来更新/删除对象的行:
UPDATE ZFOO SET Z_OPT=Y, (...) WHERE (...) AND Z_OPT=X
DELETE FROM ZFOO WHERE (...) AND Z_OPT=X
其中:
X - 存储区最后知道的版本号(来自缓存)
Y - 新版本号
如果这样的查询失败(没有行受到影响),该行将在存储的缓存中更新,并将其版本与先前缓存的版本进行比较。
我的问题是:自定义NSIncrementalStore如何通知核心数据某些更新/删除/锁定的对象发生乐观锁定失败?只有商店才能知道当它处理传递给它的NSSaveChangesRequest时,它的-executeRequest:withContext:error:方法。
如果存储下的底层数据库没有更改,那么会检测到冲突,因为 Core Data在对存储执行保存更改请求之前对每个修改/删除/锁定的对象调用-newValuesForObjectWithID:withContext:error:。我无法找到任何方法让NSIncrementalStore通知 Core Data 在它开始处理保存请求后发生了乐观锁定失败。有没有一些无证的方法可以做到这一点?在这种情况下, Core Data 似乎抛出了一些异常,然后神奇地转换为失败的保存请求,其中 NSError 列出了所有冲突。我只能通过从-executeRequest:withContext:error 返回 nil 来部分模仿这一点:并自己创建错误消息。我认为在这种情况下也必须有一种方法可以使用标准的 Core Data 冲突处理机制。