Search code examples
iphoneioscore-datansmanagedobjectcontextsave

Core Data relationship not saving even though it is set


I have a mandatory genericCall relationship for a PlaylistCall entity that is being set prior to the PlaylistCall being saved. The logs show that the genericCall is not nil and yet I get a validation error when trying to save the managed object context stating that the relationship is not set.

What could cause a relationship that is already set to then become nil during the save?

What I have verified or attempted:

  • I use a shared singleton as my managed object context for the entire app so I am definitely working within the same managed object context
  • An inverse relationship exists between GenericCall and PlaylistCall
  • On a whim, I tried deleting the relationship and then recreating it, but that did nothing so it seems like a bug in my code instead of a random bug in Xcode
  • I tried using setValue:forKey instead of the dynamic property accessors (the dot notation) with no change

Here are the logs:

2013-03-25 11:48:35.400 Project[30599:c07] Supposed to add <PlaylistCall: 0xa1be450> (entity: PlaylistCall; id: 0xa17dea0 <x-coredata:///PlaylistCall/tBB209BC5-EF9A-4DAE-9A55-0001B97499392> ; data: {
    delaySetting = 0;
    genericCall = "0xd4ad520 <x-coredata://7EB1AFB2-7B49-431E-9700-275307B4D1C1/GenericCall/p367>";
    orderInTable = 4;
    playlist = nil;
    repeatSetting = 0;
    repeatType = 0;
    volumeSetting = 100;
}) with generic call <GenericCall: 0xd4c3fd0> (entity: GenericCall; id: 0xd4ad520 <x-coredata://7EB1AFB2-7B49-431E-9700-275307B4D1C1/GenericCall/p367> ; data: {
    animalCategory = "0xc0aaa80 <x-coredata://7EB1AFB2-7B49-431E-9700-275307B4D1C1/AnimalCategory/p19>";
    callDescription = "Call Name";
    numberOfTimesOnPlaylist = 0;
    numberOfTimesPlayed = 0;
    playlistCall = "0xa17dea0 <x-coredata:///PlaylistCall/tBB209BC5-EF9A-4DAE-9A55-0001B97499392>";
    soundFile = "sound_file_name";
    timeLength = "0.21";
})
2013-03-25 11:48:35.402 Project[30599:c07] Error saving.  Reason(s): 
    PlaylistCall: The attribute 'genericCall' cannot be empty.

Here is how I am setting and saving the values:

GenericCall *genericCall = [self.fetchedResultsController objectAtIndexPath:indexPath];
PlaylistCall *playlistCall = (PlaylistCall *)[NSEntityDescription insertNewObjectForEntityForName:@"PlaylistCall" inManagedObjectContext:commonContext.managedObjectContext];
[self.playlist addPlaylistCall:playlistCall withGenericCall:genericCall orderInTable:[self.playlist.playlistCalls count]];
// Note: all other attributes are setup via model defaults
[commonContext save];

My NSManagedObject subclass for Playlist where I set the relationship:

- (void)addPlaylistCall:(PlaylistCall *)playlistCall withGenericCall:(GenericCall *)genericCall orderInTable:(int)orderInTable
{
    NSMutableOrderedSet *tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.playlistCalls];
    playlistCall.genericCall = genericCall;
    playlistCall.orderInTable = [NSNumber numberWithInt:orderInTable];
    NSLog(@"Supposed to add %@ with generic call %@", playlistCall, playlistCall.genericCall);
    [tempSet addObject:playlistCall];
    self.playlistCalls = tempSet;
}

My common managed object context singleton (CommonContext) just looks like this (and yes I've verified that the self.managedObjectContext is not nil and doesn't change):

// Saves the managed object context
- (BOOL)save
{
    NSError *anError;
    if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&anError]){
        [self handleSaveError:anError];
        return NO;
    } else {
        return YES;
    }
}

Any ideas for what I may be doing wrong? It seems straightforward and I use this same approach everywhere else in my app for setting relationships, so there has to be something that I am missing. Thanks for your time!


Solution

  • It's hard to be certain but here's a scenario that fits the symptoms:

    1. You look up an existing GenericCall instance before creating a new PlaylistCall. But this GenericCall already has a non-nil value for playlistCall.
    2. You make the assignment playlistCall.genericCall = genericCall using the new PlaylistCall.
    3. The already-existing PlaylistCall instance has its genericCall relationship set to nil-- because this is a one-to-one relationship and that's the default behavior in this case.
    4. Saving fails-- not because of the new PlaylistCall you created, which is fine, but because of the previous PlaylistCall that has been left with a dangling genericCall relationship.

    If this is what's happening, you'll need to check for an existing playlistCall and do something reasonable with it before saving changes-- most likely either reassigning its genericCall to point to something else, or possibly just deleting the instance.