为啥将 NSManagedObjectContext 保存为“干净”时会出现合并错误?

Posted

技术标签:

【中文标题】为啥将 NSManagedObjectContext 保存为“干净”时会出现合并错误?【英文标题】:Why am I getting a merge error when saving an NSManagedObjectContext this 'clean'?为什么将 NSManagedObjectContext 保存为“干净”时会出现合并错误? 【发布时间】:2011-01-27 14:14:55 【问题描述】:

提前感谢您的帮助。我今天花了很多时间来解决这个问题,我认为我对框架如何工作的理解存在严重错误。

我正在开发一个核心数据应用程序,其中实体具有父/子关系。应用程序在启动时创建一个 NSManagedObjectContext (MOC)。当应用程序第一次运行时,它使用异步块将 plist 的内容导入第二个 MOC(使用 URI 和 -managedObjectIDForURIRepresentation: 从主 MOC 获取根节点),就在块完成之前保存第二个上下文。

在我的数据控制器中,我订阅了 NSManagedObjectContextDidSaveNotification 并在发送通知时运行以下代码:

- (void)backgroundContextDidSave:(NSNotification *)notification 
    if(![notification.object isEqual:self.managedObjectContext])
        if (![NSThread isMainThread]) 
            [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                                   withObject:notification
                                waitUntilDone:NO];
            return;
           
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];        ;
    

我已经对此代码进行了完整性检查,果然,当第二个 MOC 保存时,它从执行块的线程中调用,然后被延迟,并从主线程运行。通知对象包含在第二个 MOC 中导入的所有对象,包括我们接下来要处理的两个。

完成后,我运行以下代码,该代码位于对象所属的 NSManagedObject 子类的方法中,它只是为了从其父级中删除一个子级:

TreeEntry *oldParent=self.parent; //keep a pointer to the old parent around so we can delete self from the children

// These next four lines are a sanity check to make sure that both objects are on the same MOC we're saving
NSManagedObjectContext *selfContext=self.managedObjectContext;
NSManagedObjectContext *parentContext=self.parent.managedObjectContext;
NSManagedObjectContext *sharedContext=[[DataController sharedDataController] managedObjectContext];
assert([selfContext isEqual:parentContext] && [selfContext isEqual:sharedContext]);

// now we fault the two objects to make sure we can not possibly have them or any changes 
// to them in the state of the main MOC, by this time the second MOC is long gone
[sharedContext refreshObject:self.parent mergeChanges:NO];
[sharedContext refreshObject:self mergeChanges:NO]; 

// up to this point, sharedContex.insertedObjects, sharedContext.updatedObects and sharedContext.deletedObjects
// have all contained no objects at all. None of the above was necessary as the MOC held no changes at all
[sharedContext saveChanges]; // we save it to, well, just to make sure I guess, I may be going crazy

// Now we carry out two changes to the objects, problem occurs if only one change is carried out,
// I'm showing both to show that there relationship is being kept consistent and valid
self.parent=nil;
[oldParent removeChild:self];


// When the next line is run the save fails with a merge conflict
[sharedContext saveChanges];

最后一次保存失败,出现 Cocoa 错误 133020,这是合并失败。错误中的两个 NSMergeConflict 与我们正在处理的条目(self 和 self.parent)有关。

我只是不明白这是怎么回事。对象在被修改时没有状态,因此它们必须从存储中加载。进行了两个简单的更改,然后当它们被直接保存后,就会发生合并冲突。怎么可能?没有其他东西对商店造成影响,我们只是从中加载了对象。

我知道我可以更改合并策略,但我不想在不了解发生了什么的情况下这样做。

有什么想法吗?我敢肯定这只是我的心理模型,如果发生的事情是错误的,但我一整天都无法正确设置它!

【问题讨论】:

您是否在管理对象模型中为父/子设置了反向关系?如果是这样,您将执行两次相同的操作,并且可能会通过调用 removeChild: 将您的托管对象上下文与不再存在于关系中的对象混淆。如果你想有一个真正干净的上下文,你应该使用 NSManagedObjectContext 重置,而不仅仅是 refreshObject。 兔兔,感谢您的回复。是的,我已经建立了一个反向关系,直到昨天我只有'self.parent = nil'行执行删除。我只添加了父端,以防它由于某种原因没有正确传播,我把它留在代码示例中,这样读者就不会认为合并错误是由不一致的关系引起的。 “重置”消息有点过于极端,我想了解是什么导致了我的问题。 【参考方案1】:

好吧,这是我对框架如何工作的根本误解,或者更准确地说,是 NSManagedStoreCoordinator 缓存。

当我保存后台上下文时,更改会转到磁盘,但显然 NSManagedStoreCoordinator(两个上下文共享)不会更新或使其缓存无效。

当我刷新主 MOC 中的对象时,用于重新填充它们的数据来自缓存,缓存中仍然有旧数据。它不会从磁盘重新加载。解决方案是使用 [MOC setStalenessInterval:0.0] 强制从磁盘重新加载。

【讨论】:

以上是关于为啥将 NSManagedObjectContext 保存为“干净”时会出现合并错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何在核心数据中以一对多关系链接现有值?

NSFetchedResultsController,修改NSFetchRequest?

为啥 math.inf 是浮点数,为啥我不能将其转换为整数?

为啥在链表中查找循环时将指针增加 2,为啥不增加 3、4、5?

为啥注册用户未在 Firebase 中进行身份验证?为啥用户不能将产品添加到数据库 Firebase?

为啥我们将字符串数组作为参数传递给 main() 方法,为啥不传递任何集合类型或包装类型或原始类型?