Jim
Jim

Reputation: 5960

NSManagedObjects not being saved -- Beware of Inverse Relationships

(Answer found. See below.)

In the following code, I am updating about 350,000 records by linking some of the relationships. However, in the end, I examine the sqlite database, and only a fraction of the relationships are saved. The rest are still nil.

-update-

Before describing the code in question, I should explain that the dictionaryOfSynsetDictionaries contains prefetched synset objects. It is organized as a dictionary containing four dictionaries, where the keys are 'n', 'v', 'a', and 'r' (for the four parts of speech, or pos). Each of the inner dictionaries contains references to the synset objects, which are subclassed from NSManagedObject.

Each inner dictionaries is keyed by what is called synsetOffset.

The code below fetches all of the SYNSET_POINTER objects from the store and puts them into an array. Each SYNSET_POINER object refers to a synset by the synsetOffset and partOfSpeech (pos) properties. It also contains a relationship that links the SYNSET_POINTER object to the synset by matching synsetOffset and pos to the corresponding synset in dictionaryOfSynsetDictionaries

So now, after having prefetched and organized the synset objects into the dictionary, the following code fetches all of the SYNSET_POINTER objects into an array, iterates over the array, linking synset pointers to their synset objects directly through the corresponding relationship.

enter image description here

(The picture above shows two relationships between the SYNSET and SYNSET_POINTER objects. This is based on the original dataset organization. They serve two different purposes. For this question, I am referring to the one-to-one relationship.)

-end update-

Here is the code doing the updates:

[request setEntity:[NSEntityDescription entityForName:@"SYNSET_POINTER" inManagedObjectContext:[ManagedObjectContext moc]]];
predicate = nil;
[request setPredicate:predicate];
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"synsetOffset" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSArray *synsetPointersArray = [[ManagedObjectContext moc] executeFetchRequest:request error:&error];

int i = 0;
int j = 0;

for(SYNSET_POINTER *pointer in synsetPointersArray) {
    NSString *pos = pointer.partOfSpeech;
    NSString *offset = [pointer.synsetOffset stringValue];
    pointer.synsetPointer = [[dictionaryOfSynsetDictionaries objectForKey:pos] objectForKey:offset];
    error = nil;
    if (![[ManagedObjectContext moc] save:&error]) {
        NSLog(@"error with save\n%@\n%@",error.localizedFailureReason, error.localizedDescription);
        NSLog(@"pause and quit");
    }
    NSLog(@"pos %@, offset %@, pointer %@", pos, offset, pointer);
    if (j==100) {
        NSLog(@"%@ %d", pos, i);
        j=0;
    }
    i++;
    j++;
}

Here, I am updating the property synsetPointer for the pointer of class SYNSET_POINTER, a subclass of NSManagedObject. I can see that in each iteration of the loop, the synsetPointer relationship does point to a proper object, as in this debugger output:

2012-11-26 22:03:08.753           [26156:fb03] pos v, offset 5815, pointer <SYNSET_POINTER: 0x404e84f0> (entity: SYNSET_POINTER; id: 0x404d00d0 <x-coredata://9136BC94-4D77-4DB6-B03F-4F3AA35E2E49/SYNSET_POINTER/p282133> ; data: {
    partOfSpeech = v;
    pointerSymbol = "~";
    reverseRelatedSynset = "0x244cd880 <x-coredata://9136BC94-4D77-4DB6-B03F-4F3AA35E2E49/SYNSET/p85476>";
    sourceTarget = 0000;
    synsetOffset = 5815;
    synsetPointer = "0x244cdda0 <x-coredata://9136BC94-4D77-4DB6-B03F-4F3AA35E2E49/SYNSET/p83738>";
})
2012-11-26 22:03:08.822           [26156:fb03] pos v, offset 5815, pointer <SYNSET_POINTER: 0x404e8530> (entity: SYNSET_POINTER; id: 0x404d00e0 <x-coredata://9136BC94-4D77-4DB6-B03F-4F3AA35E2E49/SYNSET_POINTER/p285862> ; data: {
    partOfSpeech = v;
    pointerSymbol = "@";
    reverseRelatedSynset = "0x244cd870 <x-coredata://9136BC94-4D77-4DB6-B03F-4F3AA35E2E49/SYNSET/p86470>";
    sourceTarget = 0000;
    synsetOffset = 5815;
    synsetPointer = "0x244cdda0 <x-coredata://9136BC94-4D77-4DB6-B03F-4F3AA35E2E49/SYNSET/p83738>";
})

The synsetPointersArray is sorted by the synsetOffset value. I can sort the table in the sqlite viewer in Firefox, and I see that most of the values remain nil. The debug output above shows they are all assigned. For some reason, they are not being saved.

Can anyone see a problem with this code that would prevent some of the updates?

Upvotes: 1

Views: 362

Answers (2)

Jim
Jim

Reputation: 5960

Solved!

It wasn't obvious from all of the information I provided, but I discovered that the one-to-one relationship shown in the picture was the problem.

SYNSET_POINTER.synsetPointer should point to one and only one SYNSET object.

But it tuns out that more than one SYNSET_POINTER object can point to an individual SYNSET object.

Therefore, the reverse relationship, SYNSET.reverseSynsetPoint, should have been configured as part of a one-to-many bidirectional relationship.

Since the managed object context takes care of resolving reverse relationships, the one-to-one relationship implies that previously assigned links were being nullified automatically. Assinging a new SYNSET_POINTER to a SYNSET that already has a SYNSET_POINTER-to-SYNSET relationship set will nullify the existing link in both directions every time the SYNSET.reverseSynSetPointer was changed. This would have left a dangling link on the SYNSET_POINTER side, but the managed object context nullifies it automatically.

So Core Data had been setting and saving the changes. But after many of these links were configured, as I expected, they also were being nullified.

In my case, I really didn't need the reverse relationship, and included it as an afterthought, thinking I might use it at a later time. My advice is to think carefully about these types of reverse relationships when they are initially set up. Otherwise, they may create trouble later.

Upvotes: 1

Mundi
Mundi

Reputation: 80265

Your code uses very poor variable names which makes it difficult to understand what you want to achieve. However, it seems you are making a fundamental conceptional mistake.

In general, to establish relationships in core data, you do not save references to other Core Data objects as attributes in those objects. Rather, you use relationships and let core data handle the technicalities of foreign keys. In order to add an object to a relationship, use the Core Data generated accessors in your object classes.

Check out these sections of the Core Data Programming Guide:
Relationships and Fetched Properties
Managed Objects

Upvotes: 0

Related Questions