user1904273
user1904273

Reputation: 4764

Save to more than one entity

For a detail view I would like to let the user leave notes for each item. The app is for a data-driven website. In the web version, the web app stores notes in a separate table with a field for the itemid.

In Core Data I have an entity of items and another entity of notes. The notes entity has an attribute called itemid. When user creates a note the first time, it stores the itemid in the note record.

My question is when you pull up the item for editing how can you simultaneously pull up the right note based on the note having a certain itemid?

In a database situation you could do a join, or from a web page you could make two separate requests to the two tables but I am somewhat flummoxed by how to do this with Core Data.

Do you have to put a relationship to the note and therefore have the noteid in the item row?

If so would you be able to access the note attribute using the item object?

Thanks in advance for any suggestions.

This is what I am using to save information. I just don't know how to make sure I'm saving it for right note.

self.managedObjectContext = [IDModel sharedInstance].managedObjectContext;
NSString *noteText = _notesView.text;
NSNumber *itemId = self.item.itemid;
// Populate Record
[self.note setValue:noteText forKey:@"note"];
[self.note setValue:itemId forKey:@"itemid"];

Model (simplified):

Item:
name NSString
itemid: Integer 64

Note:
note NSString
noteid: Integer 64
itemid: Integer 64

  Edit:

Code to try to link note and item while creating both...

//in save method
        // Create Entity
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Notes" inManagedObjectContext:self.managedObjectContext];

        // Initialize New Record ie newNote
        NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];

        // Populate Record
        [record setValue:note forKey:@"note"];
        [record setValue:localid forKey:@"localnid"];

        // Save Record
        NSError *error = nil;

        if ([self.managedObjectContext save:&error]) {
//   If note saved, save new item…
             if (itemlength>1) {
            Items *newItem = [NSEntityDescription insertNewObjectForEntityForName:@“Item” inManagedObjectContext:self.managedObjectContext];
            newItem.item = item;
              newItem.note = self.note
//This is null as note does not seem to pick up newly created note.
            if (![self.managedObjectContext save:&error]) {
                NSLog(@"Error: %@", [error localizedDescription]);
             }
             }

Upvotes: 0

Views: 103

Answers (2)

pbasdf
pbasdf

Reputation: 21536

Yes, you should use a relationship between the Item and Note entities. To create a relationship just Ctrl-drag from one entity to the other in the data model editor. Note that Xcode automatically adds an inverse relationship:

Screenshot of ERD

I've renamed the relationships for clarity - you can tailor the details of the relationship (name, one-one v one-many, delete rule, etc) in the panel on the right. In the example above, the Item entity has three properties: 2 attributes and 1 relationship. Given an Item object, say myItem, the values for these properties can be accessed using the Key Value Coding methods: valueForKey: and setValue:forKey:. For example, if attribute is defined as a string:

 NSString *myStringValue = [myItem valueForKey:@"attribute"];
 [myItem setValue:@"new value for attribute" forKey:@"attribute"];

That's very long-winded. So to make life easier, use the "Create NSManagedObject subclass..." option. Xcode will configure each entity to be a subclass of NSManagedObject and will create new class files (.h/.m or .swift) with details of the properties. For the example Item:

@property (nullable, nonatomic, retain) NSString *attribute;
@property (nullable, nonatomic, retain) NSString *attribute1;
@property (nullable, nonatomic, retain) Note *note;

The thing to realise is that the note relationship is of class Note. It's not a foreign key, or a noteid, that you have to use to lookup the corresponding Note object. It is the Note object. Under the hood, CoreData is adding primary keys and foreign keys to the underlying tables, but all that aggravation is abstracted away.

Having created the subclasses, you can use the dot-accessors for the object properties:

NSString *myStringValue = myItem.attribute;
myItem.attribute = @"new value for attribute"; 

For the relationship, if you have an Item object called myItem and a Note object called myNote, you can set the relationship value with:

myItem.note = myNote;

or equivalently:

myNote.item = myItem;

(Note: use one or the other, not both; CoreData automatically sets inverse relationships for you).

Now, you have the added complication of a web server from which the Item objects and Note objects are downloaded. And on your web server, your Notes table has a field for the itemid, which is used to link Items and Notes. At some point, you want to link up Note objects and Item objects using the itemid. The usual approach would be to do it once (as soon as the CoreData objects are synchronised from the server), set the relationship accordingly, and thenceforth use the relationship rather than the itemid to get the note for a given item. For example, if you are creating a new Note object, and the itemid from the server is "1234", you might do this:

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Item"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"itemid == %@", @"1234"];
NSError *error;
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
// should check for nil results/error
if (results.count > 0) {
    // found (at least) one Item with itemid == @"1234"
    // use the first to set the relationship
    newNote.item = results[0];
}

Then whenever you have a particular Item object, you can access the corresponding note using

Note *myNote = myItem.note;

Furthermore, you can cascade the dot-notation, so get the value of attribute for the Note for myItem, use:

NSString *noteText = myItem.note.attribute;

EDIT

Your save method is very close: either set self.note = record before you save, or use newItem.note = record:

    //in save method
    // Create Entity
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Notes" inManagedObjectContext:self.managedObjectContext];

    // Initialize New Record ie newNote
    NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];

    // Populate Record
    [record setValue:note forKey:@"note"];
    [record setValue:localid forKey:@"localnid"];

    // Save Record
    NSError *error = nil;

    if ([self.managedObjectContext save:&error]) {
    //   If note saved, save new item…
        if (itemlength>1) {
            Items *newItem = [NSEntityDescription insertNewObjectForEntityForName:@“Item” inManagedObjectContext:self.managedObjectContext];
            newItem.item = item;
            newItem.note = record;
            if (![self.managedObjectContext save:&error]) {
                NSLog(@"Error: %@", [error localizedDescription]);
            }
        }
    }

Upvotes: 1

Lorenzo B
Lorenzo B

Reputation: 33428

To reach your goal you need to create a NSFetchRequest passing it the right NSPredicate.

The fetch request will be run against your Note entity. The predicate will allow you to specify that the Note object you want to retrieve is the one for that specific noteid.

So, if you have a 1-to-1 relationship between Item and Note, the NSPredicate should like the following:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"relationshipToItem.propertyForItemId == %@", yourItemId];
[request setPredicate:predicate];

Here I suppose you've a created a relationship between the two entities, otherwise you need to do it manually. Can you provide how your model looks like?

Upvotes: 0

Related Questions