Save to more than one entity - ios

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]);
}
}

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:
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]);
}
}
}

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?

Related

IOS/Objective-C: retrieving ManagedObjectID within loop error

I am having trouble setting and retrieving a managedObjectId within a loop. First problem, I can't find in the docs what the parts of the MOID mean. So first question, are the following moids unique? The only way that they are different is in the last digit after the entity name, Item. If not, that could be the issue.
0xd000000054200000 <x-coredata://10EC1628-A6D4-487B-BF5C-61EAD9838132/Item/p5384>
0xd000000054240000 <x-coredata://10EC1628-A6D4-487B-BF5C-61EAD9838132/Item/p5385>
Second question, if they unique, when I retrieve the record associated with these ids, I end up retrieving the same record. So maybe there is a problem in the loop below.
Here is my code simplified slightly as there is a sync to server that I have not included.
//NSArray * myItems is an array of items to be saved
for (i=0;i<max;i++)
{
currentItem = myItems[i];
// Create Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
// Initialize Record
NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
// Populate Record
[record setValue:currentName forKey:#"name"];
// Save Record
NSError *error = nil;
if ([self.managedObjectContext save:&error]) {
//Set moID in ivar of saved record
self.moID = [record objectID];
[self syncAndMarkSynced];
}
}//close loop
-(void) syncAndMarkSynced{
//sync to server omitted
Items *object = [self.managedObjectContext objectRegisteredForID:self.moID];
object.synced = #1;
}
First problem, I can't find in the docs what the parts of the MOID mean.
That's because they are not documented. The object ID is unique; the details are not explained because the parts of the URI are not intended to be meaningful on their own.
Second question, if they unique, when I retrieve the record associated with these ids, I end up retrieving the same record.
That's expected. A managed object has a unique ID. When you look up a managed object by ID, you're requesting the same entry from the persistent store. Each entry has a unique ID, so if you use the ID, you get that entry.
So maybe there is a problem in the loop below.
It's not clear to me what the loop is trying to do. Hopefully the information above will help you work it out.

Entity relationship between NSManagedObjects issue

I'm running into a CoreData problem related to one-to-many relationships.
Entity 1 - Authors has a one-to-many relationship with Entity 2 - Books. I think Books has a one-to-one relationship with Authors.
Since there are multiple books per author in the author object I have
#property (nonatomic, retain) NSSet *book;
The corresponding property in the book object is:
#property (nonatomic, retain) Authors *author;
When the app downloads books from the server through an API, after saving the book, I am trying to also save the author and associate the book with the author with the following code:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Books" inManagedObjectContext:self.managedObjectContext];
NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
record setValue:bookname forKey:#"bookname"];
if ([self.managedObjectContext save:&error]) {
Authors *newAuthor = [NSEntityDescription insertNewObjectForEntityForName:#"Authors" inManagedObjectContext:self.managedObjectContext];
newAuthor.aid = authorid;
newAuthor.book = record;
}
This code has worked for me for one to one relationships, but in this case, is throwing following exception error:
'NSInvalidArgumentException', reason: 'Unacceptable type of value for
to-many relationship: property = "book"; desired type = NSSet; given
type = Books;
Can anyone suggest how to fix this?
Update:
I also tried switching it around to read:
record.author = newAuthor;
But this gives error
"property 'author' not found on object of type NSManagedObject"
although there is such a property defined in the Books object (as shown above).
Since the book property is a set, CoreData should have created an Authors method such as addBookObject:. When you created your custom entity classes, there should be a file with a name similar to "Authors+CoreDataProperties.h". Look in there for defined methods.
Your second option should also work if you use Books instead of NSManagedObject.
i.e.:
Books *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
U can generate NSManagedObject subclass, after just create few objects, set relations and save context once, someting like:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Book" inManagedObjectContext:self.context];
Book *newBook = [[Book alloc] initWithEntity:entity insertIntoManagedObjectContext:self.context];
newBook.bookName = #"My new book";
NSEntityDescription *entity2 = [NSEntityDescription entityForName:#"Record" inManagedObjectContext:self.context];
Record *newRecord = [[Record alloc] initWithEntity:entity2 insertIntoManagedObjectContext:self.context];
newRecord.name = #"New record";
newBook.record = newRecord;
[newRecord addBook:[NSSet setWithObject:newBook]];
[self.context save:nil];
This is sample for db like below:
To autogenerate classes - select Entities in *.xcdatamodel file and press File->New->File select CoreData section and NSManaged Object subclass, go by wizard steps.
You will get something like
And even more:
Good tutorial u can also found here

how to fetch data from two tables in core data objective-c

I'm new in use Core Data. I have a two entities CDContact and CDAddress. I have problem to fetch a second entity (CDAddress).
self.fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"CDContact"];
[self.fetchRequest setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES]]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchRequest managedObjectContext:[CoreDataManager sharedInstance].managedObjectContext sectionNameKeyPath:nil cacheName:nil];
[self.fetchedResultsController setDelegate:self];
[self fetchResults];
It works but I get only data from CDContact.
I try something like this (I found this on documentation):
NSManagedObjectContext *context = [CoreDataManager sharedInstance].managedObjectContext;
NSEntityDescription *contactEntity = [NSEntityDescription entityForName:#"CDContact" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = contactEntity;
request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:#"CDAddress"];
but in this version both entities doesn't work.
I'll be glad for any examples which help me resolve this problem.
As per comments, a fetch will only return results from a single entity. To access the second entity, you either need to run a second fetch, or to use the relationship on your CDContact objects. For example if the relationship is named address,
myCDContact.address
will give the CDAddress for the specified CDContact.
To expand on this, suppose your CDContact entity has attributes firstName and lastName, and a one-one relationship to CDAddress entitled address. Likewise suppose your CDAddress entity has attributes street, town, and zipCode, and the inverse relationship to CDContact entitled contact.
Then to create a new contact named "John Smith" living at "1 Main Street, AnyTown, 90210", you would have code something like this:
CDContact *myCDContact = (CDContact *)[NSEntityDescription insertNewObjectForEntityForName:#"CDContact" inManagedObjectContext:context];
myCDContact.firstName = #"John";
myCDContact.lastName = #"Smith";
CDAddress *myCDAddress = (CDAddress *)[NSEntityDescription insertNewObjectForEntityForName:#"CDAddress" inManagedObjectContext:context];
myCDAddress.street = #"1 Main Street";
myCDAddress.town = #"AnyTown";
myCDAddress.zipCode = #"90210";
// and lastly, to set the relationship between them...
// EITHER
myCDContact.address = myCDAddress;
// OR
myCDAddress.contact = myCDContact;
(Note the either/or: you only have to set the relationship "one-way", CoreData will set the inverse automatically). If you then save the context, and subsequently fetch the contacts, with something like this:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"CDContact"];
[fetch setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES]]];
NSError *error;
NSArray *results = [context executeFetchRequest:fetch error:&error];
then (assuming the fetch is successful: you should test for nil/error) the array will contain your contacts. Taking the first item in the array, you can then access its properties including the related CDAddress like this:
CDContact *myContact = (CDContact *)[results objectAtIndex:0];
NSLog(#"The contact is %# %#", myContact.firstName, myContact.lastName);
NSLog(#"who lives at %#, %#, %#", myContact.address.street, myContact.address.town, myContact.address.zipCode);
(All the above assumes you have created NSManagedObject subclasses for your entities. If you haven't, you should.)
The answer provided by pbasdf is perfect and very detailed. However, I would like to add this from the apple documentation.
To retrieve data using a managed object context, you create a fetch request. A fetch request is an object that specifies what data you want, for example, “all Employees,” or “all Employees in the Marketing department ordered by salary, highest to lowest.” A fetch request has three parts. Minimally it must specify the name of an entity (by implication, you can only fetch one type of entity at a time). It may also contain a predicate object that specifies conditions that objects must match and an array of sort descriptor objects that specifies the order in which the objects should appear...
Assuming the relationship between the entities CDContact and CDAddress is one-to-one, your CDContact interface file should look something like this:
#interface CDContact : NSManagedObject
#property (nonatomic,strong) NSString *firstName;
#property (nonatomic,strong) NSString *lastName;
#property (nonatomic,strong) CDAddress *address;
#end
This way, once you fetch a given CDContact object, you can access the related CDAddress object using the property "address". But if you need to access all addresses and all contacts, you need to perform two separate fetches.

core data - insert new object with two entities

I have two entities with a to-many relationship, there can be multiple items to each list.
List
Item
Basically all I want to achieve is to to save an Item to a specific List on click.
I've figured out how to save a new list:
- (IBAction)handleBtnAddList:(id)sender
{ MyListAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newList;
newList = [NSEntityDescription
insertNewObjectForEntityForName:#"List"
inManagedObjectContext:context];
[newContact setValue:#"Shopping" forKey:#"name"];
NSError *error;
[context save:&error]; }
But how do I save an item to the newly created list "Shopping" ?
Any help appreciated. Thanks in advance!
Hi there, finally had a chance to try this out. It's working now but running into another issue. I can add a new list, but can't seem to add an item.
I looked in the sqlite database and under the 'Item' entity the column named 'zLists' is empty. How do I get a value in there that will correspond with the 'List' that the item should be under?
This is what I've got
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *list = [NSEntityDescription
insertNewObjectForEntityForName:#"List"
inManagedObjectContext:context];
[list setValue:#"Test List" forKey:#"name"];
NSManagedObject *item = [NSEntityDescription
insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:context];
[item setValue:#"Test Item" forKey:#"name"];
I also tried adding this at the end but it crashes the app
[item setValue:#"Test List" forKey:#"lists"];
For my example to work with your code, you will need these prerequisites...
You have a Core Data model NSManagedObjectModel,
Within that model, you have an entity List,
Within that model and for that entity, you have two attributes listDate, and listText,
You have used Xcode to prepare (or have manually prepared) an NSManagedObject subclass for your entity List.
Instead of your line of code: [newContact setValue:#"Shopping" forKey:#"name"];...
you might choose to use dot notation to set attribute values in this manner...
newList.listDate = [NSDate date]; //sets the attribute to current date and time
newList.listText = <<some user input from (for example) a view controller>>;
or to match your syntax, you might choose to use key-value coding in this manner.
[newList setValue:[NSDate date] forKey:#"listDate"];
[newList setValue:<<some user input>> forKey:#"listText"];

Core Data, display to-many relationship distinctly along with aggregate count

I'm beginner in Core Data, so trying to wrap my mind around the following.
Let's say I have the following model:
Kid (1) -> (M) ToyName (car)
ToyAttributes (1) -> (M) key (color), value (black)
key (price), value (20)
key (store), value (toys r us)
ToyAttributes has 3 key/value pairs.
What I want to do is list the colors distinctly. So, if other toys are black also, I want to display black only once.
Another nice thing to do would be to display the aggregate count of how many black toys we have.
I currently have the following:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Kid" inManagedObjectContext:managedObjectContext_];
But unsure how to structure the proper KVC to find the proper key=color and get those values for every kid.
You need to set inverse relationship on your Entity, then if you use an NSPredicate to fetch the black one you will be able to follow the inverse relationship to retrieve the desired ToyName.
You minimally want to read those :
Fetching Managed Objects
Predicate Format String Syntax
This is some old code I've got, not perfect and not exactly what you are looking for, but still an example of the use of a NSPredicate.
This is a simple NSFetchRequest that will return all kListe entity ordered in some way.
- (NSFetchRequest *)requestPourListe{
NSFetchRequest *resultat = nil;
NSEntityDescription *description4Liste =
[NSEntityDescription entityForName:kListe
inManagedObjectContext:self.managedObjectContext];
resultat = [[NSFetchRequest alloc] init];
[resultat setEntity:description4Liste];
[resultat setSortDescriptors:self.sortDescriptorsPourNom];
// Si on veut faire un Fetch plus petit en mémoire (Partial Fault)
//[resultat setPropertiesToFetch:[NSArray arrayWithObject:kNom]];
return [resultat autorelease];
}
// Now time to use that request get the request
NSFetchRequest *requet4Liste = FRUtile.requestPourListe;
NSError *monErreur = nil;
// And Use it agains a NSManagedObjecContext (moc)
NSArray *lesListes = [moc executeFetchRequest:requet4Liste
error:&monErreur];
In the folloing self.list is one of the NSManagedObject that was fetch with the preceding request.
// those NSSet are set that were return from a simple relationship query
// so those set contain NSManagedObject
NSSet *desListeItems = self.liste.listeItems;
// HERE I'm fetching the relationship kRelationPrixElement on all NSManagedObject
// present in the set desListeItems (one to one relationShip)
NSSet *desPrixElements = [desListeItems valueForKey:kRelationPrixElement];
// generate the path, traverse the relationship call kRelationCommerce and
// reach it's attribute name kNom
NSString *predicateKey = [NSString stringWithFormat:#"%#.%#", kRelationCommerce, kNom];
// get an array out of one set probably not necessary
NSArray *desPrixElementsAR = [desPrixElements allObjects];
NSMutableArray *lesPrixAAfficher = [[NSMutableArray alloc] initWithCapacity:0];
for (NSString *value in self.commercesNoms) {
// in a predicate with format %K MUST be the key or keyPath and
// %# MUST be the value you are searching for
// the predicate here is done agains a many-to-one relation
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(%K MATCHES %#)", predicateKey, value];
// Now I'm applying the predicate to the array
[lesPrixAAfficher addObjectsFromArray:[desPrixElementsAR filteredArrayUsingPredicate:predicate]];
}
There is a thing that you must take into account while designing your model and your fetch request, you can follow relationship in a keyPath throughout many Entities AS LONG AS those relationship are to one relationship.
You can't use a keyPath to traverse a to-many relation, because how could Core Data know which of the entity it should follow on.
And finally when you got an NSManagedObject you can use dot notation on it to retreive it's properties and relationships.

Resources