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!


@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.

Pranav,


Any progress or, hopefully, success?


Thanks,

Brian

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?

Brian,

Please give me little more details of the issue that you are facing and also the snippets that you have tried in Pre/Post collection hooks?

 

Thanks,

Pranav

Kinvey Support

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

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

Thanks Pranav!


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

Pranav,


Same issue as the initial problem. But it now works for one to many relationships. If add a one to one relationships it gives me: CoreData: error: Failed to call designated initializer on NSManagedObject class (was doing this for one to many relationships before the update).


 Snippet in onPostFetch:  

for (var i = 0; i < count; i++) {
    //oneToOne is the entity
    response.body[i].oneToOne = nil; // Try number 1, didn't like setting nil to the oneToOne for CoreData. Gave the same error in CoreData if I didn't change it.
    response.body[i].oneToOne = []; // Try number 2, array can't initialize oneToOne from NSArray
}

  

Not sure what to make oneToOne if it's nil or there's nothing.

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! 

Brian,


Please send the code to pranav@kinvey.com.


If you can add support@kinvey.com as a collaborator to the app in the backend that’ll be great.


Also let me know the kid and the name of your app.


Thanks,

Pranav

Kinvey Support

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,


Can you please try with the latest iOS SDK and see if it works for you http://devcenter.kinvey.com/ios/downloads?


Thanks,

Pranav

Kinvey Support

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

Login or Signup to post a comment