Start a new topic

Relational Data Reference ID

I have two entities, Person and Notes, that have a one to many relationship. They both are NSManagedObjects. I believe I followed everything right from the Data Store and Core Data guides.


The problem: Whenever I try and save a person, it gives me "Reference object does not have ID set". From my understanding, person.notes is what it's referring to. But this should work according to the guides.


The code:

Person.m

 

- (NSDictionary *)hostToKinveyPropertyMapping {
    return @{
             @"entityId" : KCSEntityKeyId, //the required _id field
             //@"objectID" : @"objectID",
             //@"meta" : KCSEntityKeyMetadata,
             @"name" : @"name",
             @"initials" : @"initials",
             @"notes" : @"notes",
             };
}

+ (NSDictionary *)kinveyPropertyToCollectionMapping {
    return @{
             @"notes" : @"Notes",
             };
}

+(NSDictionary *)kinveyObjectBuilderOptions {
    // reference class map - maps properties to objects
    return @{ KCS_USE_DESIGNATED_INITIALIZER_MAPPING_KEY : @YES,
              KCS_REFERENCE_MAP_KEY : @{
                                        @"notes" : [MONotes class],
                                        }
              };
}

- (NSArray *)referenceKinveyPropertiesOfObjectsToSave {
    return @[ @"notes"];
}

+ (id)kinveyDesignatedInitializer:(NSDictionary *)jsonDocument
{
    NSString* existingID = jsonDocument[KCSEntityKeyId];
    id obj = nil;
    NSManagedObjectContext* context = defaultManagedObjectContext();
    NSEntityDescription* entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
    
    if (existingID) {
        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        [request setEntity:entity];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"entityId = %@", existingID];
        [request setPredicate:predicate];
        NSArray* results = [context executeFetchRequest:request error:NULL];
        if (results != nil && results.count > 0) {
            obj = results[0];
        }
    }
    
    if (obj == nil) {
        //fall back to creating a new if one if there is an error, or if it is new
        obj = [[MOPerson alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
    }
    
    return obj;
}

 

Notes.m

 

- (NSDictionary *)hostToKinveyPropertyMapping {
    return @{
             @"entityId" : KCSEntityKeyId, //the required _id field
             //@"meta" : KCSEntityKeyMetadata, //meta maps to metadata
             @"dateAdded" : @"dateAdded",
             @"text" : @"text",
             @"person" : @"person"
             };
}

+ (NSDictionary *)kinveyPropertyToCollectionMapping
{
    return @{@"person" : @"Person"};
}

+(NSDictionary *)kinveyObjectBuilderOptions {
    // reference class map - maps properties to objects
    return @{ KCS_USE_DESIGNATED_INITIALIZER_MAPPING_KEY : @YES,
              KCS_REFERENCE_MAP_KEY : @{@"person" : [MOPerson class]
                                        }
              };
}

+ (id)kinveyDesignatedInitializer:(NSDictionary *)jsonDocument
{
    NSString* existingID = jsonDocument[KCSEntityKeyId];
    id obj = nil;
    NSManagedObjectContext* context = defaultManagedObjectContext();
    NSEntityDescription* entity = [NSEntityDescription entityForName:@"Notes" inManagedObjectContext:context];
    
    if (existingID) {
        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        [request setEntity:entity];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"entityId = %@", existingID];
        [request setPredicate:predicate];
        NSArray* results = [context executeFetchRequest:request error:NULL];
        if (results != nil && results.count > 0) {
            obj = results[0];
        }
    }
    
    if (obj == nil) {
        //fall back to creating a new if one if there is an error, or if it is new
        obj = [[MONotes alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
    }
    
    return obj;
}

 

Thanks for the help!


I think I'm getting somewhere now but I'm getting a [NSNull managedObjectContext]: . I'm not saving anything null/nil in either entities... Does anyone have any ideas why it's giving me this error?

Since I cannot delete the original post, I'll just add the update here. How do you save a new parent and child entity at the same time? The reason why it's giving me NSNull error is because it does not save the reference to person in the Note collection when person and note are being saved for the first time. BUT when the person is already saved and I add a note, then it saves correctly and doesn't give me a NSNull error. I now have a work around but I was wondering if there is a way to save both new entities at the same time.


Thanks guys! 

I'm working with Persisting Core Data Objects to Kinvey but when I try to query/load from the KCSLinkedAppdataStore, I get "CoreData: error: Failed to call designated initializer on NSManagedObject class 'WHATEVER THE CLASS NAME IS'". Any help with this? 


This should be the last thing before I get the app fully working with Kinvey.

I'm working with Persisting Core Data Objects to Kinvey but when I try to query/load from the KCSLinkedAppdataStore, I get "CoreData: error: Failed to call designated initializer on NSManagedObject class 'WHATEVER THE CLASS NAME IS'". Any help with this? 

 

This should be the last thing before I get the app fully working with Kinvey.

Hello Brian,


One of our technical support engineers has been assigned to this problem. He will be asking you additional questions and providing feedback shortly. In the meantime, can you answer the following questions for us?


What platform are you using to develop your app?


What Kinvey SDK and version are you using to build your app?


Are you porting your app from another platform or are you building it from scratch using Kinvey?


Regards,


Billy Gee

Update: Loading and querying using KCSLinkedAppdataStore gives me this error, CoreData: error: Failed to call designated initializer on NSManagedObject class. It will also call  

+ (id)kinveyDesignatedInitializer:(NSDictionary *)jsonDocument

in Person.m and give me error, Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MONotes 0x15d7f3260> setValue:forUndefinedKey:]: the entity (null) is not key value coding-compliant for the key "dateAdded".'


This only happens when the there are relational data (Notes add to Person). Would I have to use a pre/post hook on the Person Collection so that it doesn't return Notes and then load/query Notes Collection?


Answers to your questions Billy Gee:

1) Platform = Xcode/iOS

2) Kinvey SDK/Version = 1.40.7

3) Built it from scratch and trying to add Kinvey

Brian,

Please take a look at this link that might help you debug your coding complaint crash:
http://devcenter.kinvey.com/ios/guides/troubleshooting#KeyValueCodingCompliantcrashes

Can you also post the definition of MOPerson and MONotes (.h files) so that I can review the types?  How are you fetching and saving using KCSLinkedAppdataStore - relevant code snippets would be helpful in further understanding this.

Also please take a look at this blog post that touches on data modelling:
http://www.kinvey.com/how-to-model-data-relationships-in-your-kinvey-backend/


Thanks,

Pranav

Kinvey Support

For http://devcenter.kinvey.com/ios/guides/troubleshooting#KeyValueCodingCompliantcrashes, if I unmap Notes from Person in hostToKinveyPropertyMapping, wouldn't that mess up the saving part of it? 


For http://www.kinvey.com/how-to-model-data-relationships-in-your-kinvey-backend/, it doesn't show me the code snippet and it gives me {gist:1929275.js?file=DeweyREST.txt}.


Saving

KCSLinkedAppdataStore* store = [KCSLinkedAppdataStore storeWithCollection:[KCSCollection collectionFromString:@"Person" ofClass:[MOPerson class]] options:nil];
    
    if (!person.thumbnailId) {
        NSData* data = person.thumbnail;
        [KCSFileStore uploadData:data options:nil completionBlock:^(KCSFile *uploadInfo, NSError *error) {
            if (error) {
                NSLog(@"Upload image failed: %@", [error localizedDescription]);
                person.synced = [NSNumber numberWithBool:NO];
            }
            else {
                person.thumbnailId = [uploadInfo fileId];
                person.synced = [NSNumber numberWithBool:YES];
                
                [store saveObject:person withCompletionBlock:^(NSArray *objects, NSError *error) {
                    if (error) {
                        NSLog(@"Saved Failed: %@", [error localizedDescription]);
                        person.synced = [NSNumber numberWithBool:NO];
                    } else {
                        NSLog(@"Saved Success");
                    }
                    
                    NSError* errorContext;
                    [defaultManagedObjectContext() save:&errorContext];
                } withProgressBlock:nil];
            }
        } progressBlock:nil];
    }
    else {
        [store saveObject:person withCompletionBlock:^(NSArray *objects, NSError *error) {
            if (error) {
                NSLog(@"Saved Failed: %@", [error localizedDescription]);
                person.synced = [NSNumber numberWithBool:NO];
            } else {
                NSLog(@"Saved Success");
            }
            
            NSError* errorContext;
            [defaultManagedObjectContext() save:&errorContext];
        } withProgressBlock:nil];
    }

 

Fetching

 

KCSLinkedAppdataStore* store = [KCSLinkedAppdataStore storeWithCollection:[KCSCollection collectionFromString:@"Person" ofClass:[MOPerson class]] options:nil];
    
    // Load people from the cloud
    [store queryWithQuery:[KCSQuery query] withCompletionBlock:^(NSArray *objects, NSError *error) {
        if (error) {
            NSLog(@"An error occurred on query: %@", error);
        }
        else {
            NSLog(@"Get all people success");

            for (MOPerson *person in objects) {
                if (person.thumbnailId) {
                    [KCSFileStore downloadData:person.thumbnailId completionBlock:^(NSArray *downloadedResources, NSError *error) {
                        if (error == nil) {
                            KCSFile* file = downloadedResources[0];
                            person.thumbnail = file.data;
                        } else {
                            NSLog(@"Got an error: %@", error);
                        }
                    } progressBlock:nil];
                }
            }
        }
    } withProgressBlock:nil];

 


MOPerson.h and MONotes.h    

// MOPerson.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import <KinveyKit/KinveyKit.h>
#import "ModelUtil.h"

@class MONotes;

NS_ASSUME_NONNULL_BEGIN

@interface MOPerson : NSManagedObject <KCSPersistable>

@end

NS_ASSUME_NONNULL_END

#import "MOPerson+CoreDataProperties.h"


//MONotes.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import <KinveyKit/KinveyKit.h>

@class MOPerson;

NS_ASSUME_NONNULL_BEGIN

@interface MONotes : NSManagedObject <KCSPersistable>

@end

NS_ASSUME_NONNULL_END

#import "MONotes+CoreDataProperties.h"

    

@Pranav any luck?

Would it work better if for saving, do NSManagedObject to NSObject to Kinvey. Then for updating, Kinvey to NSObject to NSManagedObject? I would think this would mitigate any problems that happen for updating because it seems like the Kinvey code is having trouble updating the relational NSManagedObjects. The app needs to be able to persist core data. So using just migrating to Kinvey is not an option.

Brian,


Just wanted to check if you have tried using the onPreSave()/onPostSave() collection hooks for this purpose? So, your app would call a save on the Person (also sending the Notes data along with it). This would first save the Person entity and then call the Person.onPostSave() collection hook. In this code, then you can update the Notes collection with the relevant notes.


In case you are saving the Notes directly and passing the person name to it, you could also implement Notes.OnPreSave() logic and in it - query the Person collection for the person name and find out the person.id, that way you will also persist the person's id in the Notes collection.


I hope I have understood your usecase correctly. If not, please feed me more details. Also let me know if you see any change moving to NSObject.

I will continue reviewing the code in more details and let you know if I have some more comments.


Thanks,

Pranav

Kinvey Support

Thanks Pranav!


How about the updating my NSManagedObject? That's my biggest problem right now.

 Hi Brian,

When you debug the app, which is the line that throws the below error?

"CoreData: error: Failed to call designated initializer on NSManagedObject class 'WHATEVER THE CLASS NAME IS’”.

If you could attach a screenshot of the debugger, that would help as I seek help from the engineering team about it.

Secondly, would it be possible for you to share a stripped down version of your Xcode project so that I can straight away use that to reproduce your issue here and seek any further help as needed.

Thanks,

Pranav

Kinvey Support

Where/who can I send the project? The attach a file only lets me attach one file.

Login or Signup to post a comment