6

I am writing an application that has four main entities that are all linked via relationships. Some are one to one, some are one to many. Upon initial load, three of the entities load their data from XML files stored locally to the application and one of the entities downloads an XML from the web and loads its data from it. When the app loads it performs a check to see if the data from each of these files is more recent than what it currently has and, if so, it will replace all current data in that entity with data from the appropriate file.

As part of my debug process during writing I have been forcing a delete of all data. When the delete function is called and all data is loaded at app launch the application runs beautifully and all entities and relationships behave exactly as they should. However, when I remove the call to the delete function and it performs the checks and tries to run from data it has stored, all of the relationships seem to disappear. In debugging this, I have found that all of the entities do contain all of the regular data that they are supposed to, they just don't have the relationships anymore. I can't figure out why in the world the relationships are saved on first load but don't retain when all data is not re-imported.

I would imagine some code would be helpful to anyone debugging, however, I'm not sure how much I should include. So, I will start by including just one of the methods called in the data loading class. If anything else would help, please let me know. Any help is very much appreciated.

UPDATED CODE: 2/25/11 (Based on Suggestions - Problem still exists) UPDATED CODE: 2/25/11 - Problem Solved

- (NSArray *) loadFeatures {

    if ([self checkForUpdate:@"Features"]) {

        [self deleteAllObjects:@"Features"];

        NSString *filePath = [self dataFilePath:FALSE withResourceName:@"Features"];
        NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
        NSError *error;
        GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];

        NSArray *featureElements = [doc.rootElement elementsForName:@"FEATURE"];
        NSMutableSet *featureSections = [[NSMutableSet alloc] init];

        for (GDataXMLElement *featureElement in featureElements) {

            NSString *featureName = nil;
            NSNumber *featureSecure = nil;
            NSNumber *featureID = nil;
            NSNumber *featureSortKey = nil;
            DisplayTypes *featureDisplayType = nil;

            NSArray *names = [featureElement elementsForName:@"NAME"];
            if (names.count > 0) {
                GDataXMLElement *firstName = (GDataXMLElement *) [names objectAtIndex:0];
                featureName = firstName.stringValue;
            } else continue;

            NSArray *secures = [featureElement elementsForName:@"SECURE"];
            if (secures.count > 0) {
                GDataXMLElement *firstSecure = (GDataXMLElement *) [secures objectAtIndex:0];
                featureSecure = [NSNumber numberWithInt:firstSecure.stringValue.intValue];
            } else continue;

            NSArray *featureIDs = [featureElement elementsForName:@"FEATUREID"];
            if (featureIDs.count > 0) {
                GDataXMLElement *firstFeatureID = (GDataXMLElement *) [featureIDs objectAtIndex:0];
                featureID = [NSNumber numberWithInt:firstFeatureID.stringValue.intValue];
            }

            NSArray *featureSortKeys = [featureElement elementsForName:@"SORTKEY"];
            if (featureSortKeys.count > 0) {
                GDataXMLElement *firstSortKey = (GDataXMLElement *) [featureSortKeys objectAtIndex:0];
                featureSortKey = [NSNumber numberWithInt:firstSortKey.stringValue.intValue];
            }

            NSArray *featureDisplays = [featureElement elementsForName:@"DISPLAYTYPEID"];
            if (featureDisplays.count > 0) {
                GDataXMLElement *firstFeatureDisplay = (GDataXMLElement *) [featureDisplays objectAtIndex:0];

                for (DisplayTypes *thisDisplayType in self.displayTypes) {
                    if (thisDisplayType.displayTypeID == [NSNumber numberWithInt:firstFeatureDisplay.stringValue.intValue]) {
                        featureDisplayType = thisDisplayType;
                    }
                }
            }

            NSArray *sectionElements = [featureElement elementsForName:@"SECTIONS"];


            for (GDataXMLElement *sectionElement in sectionElements) {

                NSArray *sectionIDs = [sectionElement elementsForName:@"SECTION"];

                for (GDataXMLElement *sectionID in sectionIDs) {
                    NSArray *thisSectionIDs = [sectionID elementsForName:@"SECTIONID"];
                    if ([thisSectionIDs count]) {
                        GDataXMLElement *thisSectionID = (GDataXMLElement *) [thisSectionIDs objectAtIndex:0];

                        for (Sections *thisSection in self.sections) {

                            if ([thisSection.sectionID isEqualToNumber:[NSNumber numberWithInt:thisSectionID.stringValue.intValue]]) {
                                [featureSections addObject:thisSection];
                            }

                        }
                    }
                }
            }


            NSManagedObjectContext *context = [self managedObjectContext];
            NSManagedObject *featureInfo = [NSEntityDescription insertNewObjectForEntityForName:@"Features" inManagedObjectContext:context];
            [featureInfo setValue:featureName forKey:@"name"];
            [featureInfo setValue:featureSecure forKey:@"secure"];
            [featureInfo setValue:featureID forKey:@"featureID"];
            [featureInfo setValue:featureSortKey forKey:@"sortKey"];
            [featureInfo setValue:featureDisplayType forKey:@"display"];
            [[featureInfo mutableSetValueForKey:@"section"] unionSet:featureSections];

            NSError *error;
            if (![context save:&error]) {
                NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
            }

            [[self.managedObjectContext objectWithID:featureDisplayType.objectID] addFeatureObject:featureInfo];
            [self.managedObjectContext save:&error];

            [featureSections removeAllObjects];


        }

        [xmlData release];
        [doc release];
        [featureSections release];

    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Features" inManagedObjectContext:[self managedObjectContext]];
    [fetchRequest setEntity:entity];

    NSError *error;
    NSArray *featureArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

    [fetchRequest release];

    return featureArray;

}

UPDATE: 5/25/2011

Per request I am posting a couple of screen shots.

1) This is what I get when the app loads after all data has been deleted and the relationships are in tact

Data Loaded Properly

2) This is what I get when the app runs again without first deleting and reloading data. The tabs at the bottom are created by one of the entities, and are titled a bit different. This happens because the relationship with the DisplayType is not present and it doesn't know what type of view controller to load and it doesn't know which icon to use for the tab.

Data Loaded - Relationships Missing

Carl
  • 1,246
  • 3
  • 21
  • 39

1 Answers1

4

Typically, you wouldn't need to explicitly set both sides of a relationship. When you're dealing with a to-many relationship, it's probably safer to add one entity at a time to the collection, instead of setting the collection all at once. So, instead of:

[featureInfo setValue:[NSSet setWithSet:featureSections] forKey:@"section"];

I would loop through the featureSections Set and add each object one by one to the section relationship of the Feature entity, e.g.:

for (Sections *aSection in featureSections) {
    // use the automatically-generated relationship mutator         
    [featureInfo addSectionsObject:aSection];
}

I hope this helps...

Otherwise, this section in the Apple documentation might be of interest.

octy
  • 6,525
  • 1
  • 28
  • 38
  • Thank you for the response. The only problem with the recommendation that I can see is that featureInfo is a member of the NSManagedObject class and not the Features class, and thus, does not have a member method "addSectionsObject:" - I will certainly check out the documentation. – Carl May 24 '11 at 16:03
  • I did try implementing this in the code below the initial insert where the object is fetched. Instead of loading that feature as the displays feature I just moved the setValue to that point. Unfortunately, this doesn't seem to have had any effect. – Carl May 24 '11 at 16:21
  • You *do* have access to dynamically generated accessors such as `addSectionsObject:` even when you work with `NSManagedObject` instances. (See this for an explanation and how to suppress compiler warnings: http://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdAccessorMethods.html#//apple_ref/doc/uid/TP40002154-SW9). – octy May 24 '11 at 19:47
  • Note that if you don't want to use the dynamically generated accessors that CoreData provides, you could set the elements of a to-many relationship using `mutableSetValueForKey:`, e.g.: `[[featureInfo mutableSetValueForKey:@"section"] unionSet:featureSections];`. Please let me know if this still doesn't work. – octy May 24 '11 at 20:02
  • Thanks for the response. I tried this as you suggested to no avail. I am also having a similar problem with a line a few above this one: [featureInfo setValue:featureDisplayType forKey:@"display"]; - the strange thing is that it loads when no data is present and sets everything perfectly. It's only on subsequent launches that it doesn't retain the relationships. – Carl May 24 '11 at 20:18
  • @octy, I'm not sure if there is a way to let you know that I have responded, other than to just respond. Still fairly new to this site. – Carl May 24 '11 at 20:23
  • Would you be able to post a screenshot of correct vs. incorrect output that is related to the code in your question? – octy May 25 '11 at 01:27
  • About this line of code: `[thisFeature.display addFeatureObject:thisFeature];` - You shouldn't have to do this if the relationship is configured both ways, since you already did `[featureInfo setValue:featureDisplayType forKey:@"display"];`. How is the relationship between the Features and DisplayTypes entities modeled? Have you configured both ends of the relationship correctly? – octy May 25 '11 at 14:34
  • @octy - I have actually removed that section of code, based on that suggestion earlier - I have posted the current code running in the question above as noted, in place of the original code. I believe that the relationships are set up correctly. I will post a description of how that particular relationship is set up shortly. – Carl May 25 '11 at 15:18
  • @octy - here is how the relationships are set up for these two entities: the Features entity has a relationship named "display" with a destination of "DisplayTypes" and an inverse of "feature" - the DisplayTypes entity has a relationship named "feature" with a destination of "Features" and an inverse of "display" – Carl May 25 '11 at 15:22
  • I suppose the Features - DisplayTypes relationship is one-to-one, correct? In that case, I have to say, it puzzles me. Your managedObjectContext - is it properly configured? Have you checked that the data is actually present in the database file after a save? – octy May 25 '11 at 15:41
  • @octy - actually, it is one to many - one display type may have many features, where each feature has one display type - perhaps there is something else I should do for that? All I did was check the "to many" box for the appropriate entity - when I generated the NSManagedObject Subclass for DisplayTypes it contained "features" as an NSSet and the Subclass for Features contained "DisplayTypes" as a property of class "DisplayType" - this seemed appropriate and sufficient for me... – Carl May 25 '11 at 16:17
  • I have not actually checked the database file myself after a save because I have not yet been able to find where the actual database is saved. – Carl May 25 '11 at 16:19
  • Ok, so let's backtrack a little. I see you link every new Features entity to a DisplayTypes instance you pick from the `self.displayTypes` collection. What are the contents of that collection? Are those valid managed objects? – octy May 25 '11 at 17:44
  • Yes, those are valid managed objects that are collected and saved in much the same manner previous to this method's being called. – Carl May 25 '11 at 17:51
  • BTW, the database file should be stored in the `Documents` directory, on the simulator that is: `/Users//Library/Application Support/iPhone Simulator/User/Applications//Documents` – octy May 25 '11 at 18:06
  • Going back to the problem... What happens if you try this: replace `[featureInfo setValue:featureDisplayType forKey:@"display"];` with `[(DisplayTypes *)[self.managedObjectContext objectWithID:featureDisplayType.objectID] addFeaturesObject:featureInfo];` ? – octy May 25 '11 at 18:16
  • This didn't make any difference either - I will take a look at the database file and let you know what I find out – Carl May 25 '11 at 18:26
  • Looking for the database file - and I remember now that this is why I couldn't find it before - for some reason, under this folder: /Users//Library/Application Support/iPhone Simulator/User - I only have 4 four folders: Media, Media.previousinstall, Library, Library.previousinstall - and it doesn't seem to pick up the trail again under any of these folders – Carl May 25 '11 at 18:30
  • OK - I found the solution - I am posting now in my question - I used your suggestion with assigning the object back to the displayType, but I put it after the initial save and then saved again - don't know if it's sloppy or not, but it finally works – Carl May 25 '11 at 19:01
  • It was strange because I made the change posted above and ran it. Then I checked the database and saw that the displayTypes field was populated in the features table. Then I commented out the delete code (which will cause it to run on already stored data) and ran it again. Then I checked the database and saw that the displayTypes field in the features table was null for every record. So I uncommented the delete code and ran it again and saw the data had returned. Then I figured I would step through the code with breakpoints, comment the delete code again and see exactly where it was deleting.. – Carl May 25 '11 at 19:15
  • When I stepped through the code and checked at every step the data was still there, all the way to completing the launch. Then I took out all the breakpoints and ran it again and it's been running properly ever since. It had to be that last code change, but it was odd that it didn't seem to work on the first test. – Carl May 25 '11 at 19:17
  • Glad it works! ... However, is it true that `displayTypes` is *not* a mandatory field in the Features table? If so, why is that? Does your model dictate that? Otherwise, it's best to set the mandatory flag where you have an ownership relation of one entity toward another - CoreData will use that information to maintain your database in a coherent state. In your case, it also helps you insure that a Features entity cannot be saved if the displayTypes relationship is not set... – octy May 25 '11 at 19:33
  • Done - and thanks for the info on making those properties required - I will certainly do so – Carl May 25 '11 at 19:56