我正在实现一个自定义 NSIncrementalStore 子类,它使用关系数据库进行持久存储。我仍在努力的一件事是支持乐观锁定。
(可以在下面的问题中跳过这个冗长的描述)
我分析了Core Data的SQLite增量存储如何通过检查它生成的SQL日志来解决这个问题,并得出以下结论:
数据库中的每个实体表都有一个 Z_OPT 列,它指示从1(初始插入)开始修改此实体(行)的特定实例的次数。
每次修改托管对象时,相应数据库行中的 Z_OPT 值都会递增。
商店维护 NSIncrementalStoreNode 实例的缓存(在Core Data docs中称为行缓存),每个实例都有版本属性等于 SELECT 返回的 Z_OPT 值或 UPDATE 在托管对象的行上查询。
当从 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在开始处理保存请求后发生了乐观锁定失败。是否有一些无证的方法可以做到这一点?在这种情况下,核心数据似乎会抛出某些异常,然后神奇地将其转换为失败的保存请求,NSError列出了所有冲突。我只能通过从 -executeRequest:withContext:error:返回nil并自己创建错误消息来模仿。我认为在这种情况下必须有一种方法来使用标准的Core Data冲突处理机制。
答案 0 :(得分:1)
我意识到这不是你问题的答案,但我会尝试给你我对CoreData的观点以及与数据库的关联:
(一级缓存)
NSPesistentStoreCoordinator + NSPersistentStore ==与数据库的单一连接
(二级缓存)
NSManagedObjectContext ==缓存包含更改的连接
因此,根据我的理解,您的问题是您与商店有多个连接,每个连接都进行了更改,但您对记录没有中央版本控制权。
您的商店将收到-executeRequest:withContext:error:
NSSaveRequestType
的商品
然后,您将负责验证记录版本是否匹配,如果在连接级别(级别1)中发现冲突,则报告上下文(级别2)与协调器之间的版本不匹配。
您需要在连接(级别1)和商店之间报告版本不匹配
为了能够这样做,您的商店必须在其所有连接(ConnectionManager)上报告对其的更改,或者它可能提供对其执行的更改的挂钩。
我不是SQLite专家,但SQLite API确实在该领域提供了一些东西:
update hook
commit hook
changes
total changes
(我没有设置这种钩子的经验,但如果CoreData使用它们,它将不会显示在调试日志中)
您可以通过设置错误指针(NSError **)并将其内部数据设置为与CoreData协调员正在设置的内容数据相匹配来报告这些错误(创建合并冲突并根据需要在其中设置信息)
< / p>
请注意,乐观锁定失败只会在-executeRequest:withContext:error:
期间发生
(除非你有一个流氓连接到商店,一个没有经理跟踪。)
为了支持这种行为,您的经理可能需要验证每个记录,因为它已经提交了保存[巨大的性能成本],或者对最近对记录所做的更改使用了一些挂钩
)
要处理与商店的多个连接,您可能需要拥有一个由商店网址键入的NSIncrementalStoreNode共享缓存:
静态@ {
url1:actualCacheMapping1,
url2:actualCacheMapping2,
...
}
保存到商店的每个连接将再次验证商店网址的实际缓存。
希望这对你有所帮助。
答案 1 :(得分:1)
我的问题是:自定义NSIncrementalStore如何通知Core Data一些更新/删除/锁定对象发生了乐观锁定失败?只有商店能够告诉它在处理传递给它的NSSaveChangesRequest时它的-executeRequest:withContext:error:method。
在NSIncrementalStore
中,NSIncrementalStoreNode
代表商店快照。节点的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服务或数据库等。)