NSIncrementalStore子类中的乐观锁定支持



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


(可以跳过这个冗长的描述,直接回答下面的问题)

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

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

  • 每修改一次管理对象,对应数据库行中的Z_OPT值递增。

  • 存储维护NSIncrementalStoreNode实例的缓存(在Core Data文档中称为行缓存),每个实例的版本属性等于Z_OPT值,该值由先前的SELECTUPDATE SQL查询在管理对象的行上返回。

  • 当管理对象从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通知Core Data乐观锁定失败已经发生了一些更新/删除/锁定的对象?只有存储能够告诉当它处理NSSaveChangesRequest传递给它的-executeRequest:withContext:error:方法。

如果存储下的底层数据库没有更改,那么在执行存储上的保存更改请求之前,Core Data会对每个修改/删除/锁定的对象调用-newValuesForObjectWithID:withContext:error:,因此会检测到冲突。我无法为NSIncrementalStore找到任何方法来通知Core Data,在开始处理保存请求后,发生了乐观锁定失败。是否有一些未记录的方法来做到这一点?Core Data似乎抛出一些异常在这种情况下,然后神奇地转化为失败的保存请求与NSError列出所有冲突。我只能通过从-executeRequest:withContext:error:返回nil并自己创建错误消息来部分模仿这一点。我认为在这种情况下也必须有一种方法来使用标准的Core Data冲突处理机制。

我意识到这不是你问题的答案,但我会尽量给你我对CoreData和数据库相关性的看法:

(第一级缓存)
nspersistentstorecoordinator + NSPersistentStore ==到数据库的单个连接

(二级缓存)
NSManagedObjectContext ==缓存的连接持有的变化

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


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

注意,乐观锁定失败只会在-executeRequest:withContext:error:期间发生(除非您有一个非法连接到商店,一个不被管理员跟踪的连接。)
为了支持这种行为,你的经理可能需要在每条记录提交时验证它,以节省(巨大的性能成本),或者使用一些挂钩到最近对记录所做的更改)

要处理到你的商店的多个连接,你可能需要有一个NSIncrementalStoreNode的共享缓存,由商店url关键字:
静态@ {
url1: actualCacheMapping1,
url2: actualCacheMapping2,

}
保存到存储的每个连接都将根据存储url实际缓存进行验证。

希望这对你有意义。

我的问题是:如何自定义NSIncrementalStore通知核心数据乐观锁定失败已经发生了一些更新/删除/锁定的对象?只有存储能够告诉它,当它处理NSSaveChangesRequest传递给它的-executeRequest:withContext:error:方法。

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

如果在存储过程中检测到乐观锁定失败,您的存储负责创建描述它的NSMergeConflict对象。这些将通过NSPersistentStoreCoordinator向上传播。

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

快照字典应该包括所有建模的属性属性名作为键及其值。这并不包括人际关系。对于一些存储,使用引用对象或NSIncrementalStoreNodes中的值可能就足够了,只要它们只包括建模属性的属性名作为键(这些很容易从实体描述中获得)。

创建完这些对象后,在NSCocoaErrorDomain中创建一个NSError,代码为NSPersistentStoreSaveConflictsError。userInfo对象应该包含键NSPersistentStoreSaveConflictsErrorKey,它应该包含NSMergeConflict对象的数组。从保存请求返回该值,NSPersistentStoreCoordinator将负责查找解析。请记住,您不应该为NSManagedObjectContext中的对象状态和您的存储之间的冲突生成合并冲突,只有在存储中的任何内存或缓存状态与数据保存或持久化(如web服务或数据库等)之间的冲突

最新更新