5

我正在实现一个自定义NSIncrementalStore子类,它使用关系数据库进行持久存储。我仍然在努力解决的一件事是对乐观锁定的支持。


(请随意跳过这个冗长的描述,直接回答我下面的问题)

我分析了 Core Data 的 SQLite 增量存储是如何通过检查它产生的 SQL 日志来解决这个问题的,并得出以下结论:

  • 数据库中的每个实体表都有一个Z_OPT列,该列指示该实体(行)的特定实例已被修改的次数,从 1(初始插入)开始。

  • 每次修改托管对象时,其相应数据库行中的Z_OPT值都会增加。

  • 存储维护NSIncrementalStoreNode实例的缓存(在 Core Data 文档中称为行缓存) ,每个实例都有一个版本属性,该值等于先前对托管对象行的SELECTUPDATE 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 冲突处理机制。

4

2 回答 2

1

我意识到这不是您问题的答案,但我会尝试向您提供我对 CoreData 和与数据库的相关性的观点:

(一级缓存)
NSPsistentStoreCoordinator + NSPersistentStore == 单个数据库连接

(二级缓存)
NSManagedObjectContext == 缓存在保持更改的连接上

因此,据我了解,您的问题是您与商店有多个连接,每个都进行更改,但您对记录没有中央版本控制。您的商店将收到一个-executeRequest:withContext:error:withNSSaveRequestType
然后您将负责验证记录版本是否匹配,如果您发现连接级别(级别 1)存在冲突,则报告上下文(级别 2)和协调器之间的版本不匹配。
您需要报告您的连接(级别 1)和您的商店之间的版本不匹配。
为了能够做到这一点,您的商店必须报告它的所有连接上的更改(ConnectionManager),或者它可能会提供对它执行的更改的挂钩。
我不是 SQLite 专家,但 SQLite API 确实在该领域提供了一些东西:
更新挂钩
提交挂钩
更改
总更改
(我没有设置此类挂钩的经验,但如果 CoreData 使用它们,它将不会显示在调试日志中)

您可以通过设置错误指针 (NSError**) 并设置其内部数据以匹配 CoreData 协调器正在设置的数据来报告这些错误(创建合并冲突并根据需要在其中设置信息)

请注意,乐观锁定失败只会发生在-executeRequest:withContext:error: (除非您与商店有恶意连接,经理不跟踪该连接。
为了支持这种行为,您的经理可能需要在提交保存时验证每条记录[巨大性能成本],或者在最近对记录所做的更改中使用一些挂钩)

要处理到您的商店的多个连接,您可能需要有一个 NSIncrementalStoreNode 的共享缓存,以商店 url 为键:
static @{
url1 : actualCacheMapping1,
url2 : actualCacheMapping2,
...
}
每个保存到商店的连接都将再次验证存储 url 实际缓存。

希望这对你有一些意义。

于 2013-04-05T11:17:30.537 回答
1

我的问题是:自定义 NSIncrementalStore 如何通知 Core Data 一些更新/删除/锁定对象发生乐观锁定失败?只有商店才能知道当它处理传递给它的 NSSaveChangesRequest 时,它的 -executeRequest:withContext:error: 方法。

在 anNSIncrementalStore中,NSIncrementalStoreNodes 表示存储快照。节点的version属性是乐观锁定原语。持久存储负责在存储级别检测乐观锁定失败,而托管对象上下文可以在更高的位置检测它们。如果存储正在与之交谈的系统被其他东西更改,并且该系统的状态与持久存储中的状态表示之间存在冲突,则可能会发生存储级别的乐观锁定失败。例如,如果商店正在与 Web 服务通信并且 Web 服务数据被另一个用户更改,等等。

如果在保存期间在您的商店实现中检测到乐观锁定失败,您的商店负责创建NSMergeConflict描述它的对象。这些将由NSPersistentStoreCoordinator.

[[NSMergeConflict alloc] initWithSource:managedObject newVersion:newVersion oldVersion:oldVersion cachedSnapshot:inMemorySnapshot persistedSnapshot:storedSnapshot];

快照字典应包括所有建模的属性属性名称作为键及其值。这不包括关系。对于某些商店,使用来自引用对象或 NSIncrementalStoreNodes 的值可能就足够了,只要它们只包含建模的属性属性名称作为键(并且这些很容易从实体描述中获取)。

NSError创建这些对象后,NSCocoaErrorDomain使用代码创建一个NSPersistentStoreSaveConflictsError。userInfo 对象应包含NSPersistentStoreSaveConflictsErrorKey应包含对象数组的键NSMergeConflict。从保存请求中返回它,然后NSPersistentStoreCoordinator将负责寻找解决方案。请记住,您不应该为存储中的对象状态之间的冲突生成合并冲突NSManagedObjectContext,仅针对存储中的任何内存或缓存状态与保存或持久化数据的位置之间的冲突(如 Web 服务,或数据库等)

于 2014-09-24T03:35:20.030 回答