NSInvalidArgumentException:非法尝试在不同上下文中的对象之间建立关系

Posted

技术标签:

【中文标题】NSInvalidArgumentException:非法尝试在不同上下文中的对象之间建立关系【英文标题】:NSInvalidArgumentException: Illegal attempt to establish a relationship between objects in different contexts 【发布时间】:2010-03-10 12:00:43 【问题描述】:

我有一个基于 CoreDataBooks 示例的应用程序,它使用 addingManagedObjectContextIngredient 添加到 Cocktail 以撤消整个添加。 CocktailsDetailViewController 反过来调用BrandPickerViewController 来(可选地)为给定成分设置品牌名称。 CocktailIngredientBrand 都是 NSManagedObjectsCocktail 至少需要设置一个Ingredient (baseLiquor),所以我在创建Cocktail 时创建它。

如果我在CocktailsAddViewController : CocktailsDetailViewController 中添加Cocktail(保存时合并到鸡尾酒托管对象上下文中)而不设置baseLiquor.brand,那么它可以从选择器中设置Brand(也存储在托管的鸡尾酒中上下文)稍后来自CocktailsDetailViewController

但是,如果我尝试在CocktailsAddViewController 中设置baseLiquor.brand,我会得到:

由于未捕获的异常而终止应用程序 'NSInvalidArgumentException',原因: '非法尝试建立一个 对象之间的关系“品牌” 在不同的情况下'

来自this question我了解到问题是Brand存储在应用程序的managedObjectContext中,而新添加的IngredientCocktail存储在addingManagedObjectContext中,并且通过ObjectID相反会避免崩溃。

我不明白的是如何一般地实现选择器,以便在添加期间可以设置所有成分(baseLiquormixergarnish 等),以及一个-在创建Cocktail 之后,从CocktailsDetailViewController 中删除一个。换句话说,按照 CoreDataBooks 示例,在添加和编辑情况下,ObjectID 将在何时何地从父 MOC 转换为 NSManagedObject? -IPD

更新 - 这是添加方法:

- (IBAction)addCocktail:(id)sender 

    CocktailsAddViewController *addViewController = [[CocktailsAddViewController alloc] init];
    addViewController.title = @"Add Cocktail";
    addViewController.delegate = self;

    // Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller's context.
    NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
    self.addingManagedObjectContext = addingContext;
    [addingContext release];

    [addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];

    Cocktail *newCocktail = (Cocktail *)[NSEntityDescription insertNewObjectForEntityForName:@"Cocktail" inManagedObjectContext:self.addingManagedObjectContext];
    newCocktail.baseLiquor = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:self.addingManagedObjectContext];
    newCocktail.mixer = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:self.addingManagedObjectContext];
    newCocktail.volume = [NSNumber numberWithInt:0];
    addViewController.cocktail = newCocktail;

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addViewController];

    [self.navigationController presentModalViewController:navController animated:YES];

    [addViewController release];
    [navController release];


这是Brand 选择器中的崩溃位置(此NSFetchedResultsController 由应用委托的托管对象上下文支持:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.accessoryType = UITableViewCellAccessoryCheckmark;

    if ([delegate respondsToSelector:@selector(pickerViewController:didFinishWithBrand:forKeyPath:)]) 
    
        [delegate pickerViewController:self 
               didFinishWithBrand:(Brand *)[fetchedResultsController objectAtIndexPath:indexPath] 
                            forKeyPath:keyPath]; // 'keyPath' is @"baseLiquor.brand" in the crash
    

最后是委托实现:

- (void)pickerViewController:(IngredientsPickerViewController *)pickerViewController
          didFinishWithBrand:(Brand *)baseEntity
                  forKeyPath:(NSString *)keyPath


    // set entity
    [cocktail setValue:ingredient forKeyPath:keyPath];  

    // Save the changes.
    NSError *error;
    if (![cocktail.managedObjectContext save:&error]) 
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    

    // dismiss picker
    [self.navigationController popViewControllerAnimated:YES]

更多

我正在根据 Marcus 的建议取得进展——我将 addingManagedObjectContexts 映射到父 managedObjectContext 并将所有内容包装在 begin/endUndoGrouping 中以处理取消与保存。

但是,要创建的对象在NSFetchedResultsController 中,所以当用户点击“+”按钮添加Cocktail 时,(可能要撤消的)实体会短暂出现在表格中视图作为模态添加视图控制器呈现。 MDN 示例是基于 Mac 的,因此它不涉及此 UI 行为。我该怎么做才能避免这种情况?

【问题讨论】:

为什么会有两个托管对象上下文?那很少使用并且是相当高级的东西。我在您对问题空间的描述中看到的任何内容似乎都不需要单独的上下文。 我不再使用了,但这就是 Apple CoreDataBooks 示例中用于允许中间撤消的方法——撤消编辑、撤消编辑、撤消(从不)添加。使用单个上下文和撤消分组,我仍在努力恢复中间撤消(撤消整个字段编辑。) 【参考方案1】:

听起来您正在创建两个不同的核心数据堆栈(NSManagedObjectContextNSManagedObjectModelNSPersistentStoreCoordinator)。您想要从示例中做的只是创建两个指向同一个NSPersistentStoreCoordinatorNSManagedObjectContext 实例。这将解决这个问题。

NSManagedObjectContext 视为便笺簿。您可以拥有任意数量的数据,如果在保存之前将其丢弃,则其中包含的数据将不复存在。但它们都保存到同一个地方。

更新

不幸的是,CoreDataBooks 是一个非常糟糕的例子。但是,对于您的问题,我建议 删除 创建附加上下文并查看是否发生错误。根据您发布的代码,我假设您直接从 Apple 的示例中复制的代码,双重上下文虽然几乎没用,但应该可以正常工作。因此,我怀疑还有其他事情在起作用。

尝试使用单一上下文,看看问题是否仍然存在。您可能还有其他一些有趣但微妙的错误导致您出现此错误;也许是某个地方的过度释放或类似的东西。但第一步是删除双重上下文,看看会发生什么。

更新 2

如果即使使用单个 MOC 也会崩溃,那么您的问题与上下文无关。使用单个 MOC 时遇到的错误是什么?当我们解决了这个问题时,我们就会解决您的整个问题。

至于更好的解决方案,请改用NSUndoManager。这就是它的设计目的。苹果真的不应该在他们的例子中推荐多个 MOC。

我最近在这里回答了一个关于将 NSUndoManager 与 Core Data 结合使用的问题,但您也可以查看我在 MDN 上的一些文章作为示例。

【讨论】:

别以为是这样 - managedObjectContextaddingManagedObjectContext 都设置为与 SDK 示例中相同的 NSPersistentStoreCoordinator 您发布的错误是由一种情况引起的;两个NSPersistentStoreCoordinator 实例。也许如果您共享构建上下文的代码,这将有助于显示您在做什么。 仍然很难过。只有一个NSPersistentStoreCoordinator(由应用程序委托分配和拥有)、一个主控NSManagedObjectContext(由应用程序委托分配和拥有)和一对addingManagedObjectContexts被分配在NSFetchedResultsController中并由应用程序初始化代表的persistentStoreCoordinator。当一个已经保存在主 MOC 中的 Brand 分配给在添加 MOC 中创建但尚未保存到主 MOC 的新 Ingredient 时,就会发生崩溃。你想看哪个 MOC 建设电话? 辅助上下文的创建会有所帮助 看到你的更新——我已经在你第一次回答之后尝试过了,它崩溃得更厉害(这与 NSNotificationCenter 和从 Apple 示例继承的获取结果控制器的交互有关。)你能向 CoreDataBooks 推荐一个保留 undo-an-entire-add 行为的替代示例/教程?【参考方案2】:

是的,在设置对象之间的关系时,您绝对不想跨越上下文边界;它们都需要在同一个 NSManagedObjectContext 中。在旧的 EOF 中,有用于将对象错误放入不同上下文的 API,但看起来 CoreData 没有等效的 API。

【讨论】:

以上是关于NSInvalidArgumentException:非法尝试在不同上下文中的对象之间建立关系的主要内容,如果未能解决你的问题,请参考以下文章

将 JSON 作为参数传递 swift