Note that there are some explanatory texts on larger screens.

plurals
  1. PONSInvalidArgumentException: Illegal attempt to establish a relationship between objects in different contexts
    text
    copied!<p>I have an app based on the CoreDataBooks example that uses an <code>addingManagedObjectContext</code> to add an <code>Ingredient</code> to a <code>Cocktail</code> in order to undo the entire add. The <code>CocktailsDetailViewController</code> in turn calls a <code>BrandPickerViewController</code> to (optionally) set a brand name for a given ingredient. <code>Cocktail</code>, <code>Ingredient</code> and <code>Brand</code> are all <code>NSManagedObjects</code>. <code>Cocktail</code> requires at least one <code>Ingredient</code> (<code>baseLiquor</code>) to be set, so I create it when the <code>Cocktail</code> is created.</p> <p>If I add the <code>Cocktail</code> in <code>CocktailsAddViewController : CocktailsDetailViewController</code> (merging into the Cocktail managed object context on save) without setting <code>baseLiquor.brand</code>, then it works to set the <code>Brand</code> from a picker (also stored in the Cocktails managed context) later from the <code>CocktailsDetailViewController</code>.</p> <p>However, if I try to set <code>baseLiquor.brand</code> in <code>CocktailsAddViewController</code>, I get:</p> <blockquote> <p>Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'brand' between objects in different contexts'</p> </blockquote> <p>From <a href="https://stackoverflow.com/questions/1136388/passing-objects-between-managed-object-contexts">this question</a> I understand that the issue is that <code>Brand</code> is stored in the app's <code>managedObjectContext</code> and the newly added <code>Ingredient</code> and <code>Cocktail</code> are stored in <code>addingManagedObjectContext</code>, and that passing the <code>ObjectID</code> instead would avoid the crash.</p> <p>What I don't get is how to implement the picker generically so that all of the Ingredients (<code>baseLiquor</code>, <code>mixer</code>, <code>garnish</code>, etc.) can be set during the add, as well as one-by-one from the <code>CocktailsDetailViewController</code> after the <code>Cocktail</code> has been created. In other words, following the CoreDataBooks example, where and when would the <code>ObjectID</code> be turned into the <code>NSManagedObject</code> from the parent MOC in both add and edit cases? -IPD</p> <p><strong>UPDATE</strong> - Here's the add method:</p> <pre><code>- (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]; } </code></pre> <p>and here's the site of the crash in the <code>Brand</code> picker (this <code>NSFetchedResultsController</code> is backed by the app delegate's managed object context:</p> <pre><code>- (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 } } </code></pre> <p>and finally the delegate implementation:</p> <pre><code>- (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:&amp;error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); // Fail } // dismiss picker [self.navigationController popViewControllerAnimated:YES] } </code></pre> <p><strong>EVEN MORE</strong></p> <p>I'm making progess based on Marcus' suggestions -- I mapped the <code>addingManagedObjectContexts</code> to the parent managedObjectContext and wrapped everything in <code>begin/endUndoGrouping</code> to handle cancel vs. save. </p> <p>However, the object to be created is in an <code>NSFetchedResultsController</code>, so when the user hits the "+" button to add the <code>Cocktail</code>, the (possibly-to-be-undone) entity briefly appears in the table view as the modal add view controller is presented. The MDN example is Mac-based so it doesn't touch on this UI behavior. What can I do to avoid this?</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload