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.
Related
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?
I'm very new to core data and the project that I'm currently working on involves using core data for local data storage.
Below is the structure of my entities.
My app functions as follows. When the user first launches the app, there are presented with a login screen. New users can register. When a user tries to register, they have to enter a unique key that has already been provided to them, prior to the registration. This unique key is validated in the Facility table. If the key exists, in the db, then I will enter the user data into the database and will create a relationship between the Assessor and the Facilitythat they just entered the code for.
My question is, how would I create a relationship between a new Assessor and an existing Facility?
I'm thinking, I should fetch the required Facility object from core data and then use [assessorsetValue:facilityObjectforKey:#"facility"] to set the object.
Can anyone help me understand how to fetch the facilityObject from the Facility table in core data before assigning it to the Assessor?
Thanks in advance.
EDIT:
For example:
This is how my Facility table will look like.
code | name
H6DJ | Computing
So if the user (John Doe) enters H6DJ as the registration code, I check the Facility table to see if it exists. so in this case, I would like to create a relationship between the new user (John Doe) and the Computing facility.
The Core Data Programming Guide provides lots of sample code.
Your idea about setting the facility property of the new assessor entity is correct - you just need to fetch the facility first - presumably you would need to do this anyway to validate the facility code that was entered.
NSManagedObject *newAssessor = [NSEntityDescription
insertNewObjectForEntityForName:#"Assessor"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Facility"
inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"code == %#", targetCode];
[request setPredicate:predicate];
NSError *error;
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
if (array != nil) {
NSUInteger count = array.count;
if (count == 1) {
NSManagedObject *facility=array[0];
if (facility["assessor"]==nil) {
newAssessor["facility"]=facility;
// Set up rest of assessor attributes before saving
} else {
// Error - facility already assigned to assessor ?
}
} else if (count == 0) {
// Code not found/valid - do something
} else {
// More than 1 matching facility - data error
}
}
else {
// Deal with error.
}
I have a managed object subclass ("item") which was created as follows:
Item *item = [[item alloc] init];
item.v1 = #"value1";
item.v2 = #"value2";
item.v3 = #"value3";
So, now I have this and I would like to insert it into a managed object context. I can do this:
Item *newitem = [NSEntityDescription insertNewObjectForEntityName:#"Item" inManagedObjectContext:_context]
newitem.v1 = item.v1;
newitem.v2 = item.v2;
newitem.v3 = item.v3;
[_context save:&error]
But surely there is a better way, no? Can I not just insert item without doing a field-by-field copy?
TIA
EDIT: I'm adding some context to the question:
The point is I ALREADY HAVE an Item object that I decide to insert after it has been loaded with 20 or so fields worth of data. I want to be able to insert it as a managed object. I don't want to insert the managed object until AFTER the Item object has been loaded up.
Geez. Not sure what's with the downvotes. Apparently my question wasn't worded well, I don't know.
Taking one single look in Apple CoreData documentation will show you that you don't need the first block of code. Just do like follows:
Item *newitem = [NSEntityDescription insertNewObjectForEntityName:#"Item" inManagedObjectContext:_context]
newitem.v1 = #"value1";
newitem.v2 = #"value2";
newitem.v3 = #"value3";
[_context save:&error]
That is it. If you not have done so far, start learning the above documentation by heart. Otherwise you will face many disasters with CoreData!
The problem with your first block of code, is that it is not the normal way of creating an NSManagedObject. You need to give it an EntityDescription in order to later insert it into an NSManagedObjectContext.
If you for some reason need to create temporary NSManagedObject that you do not wish to insert, you can do it by passing nil for the context parameter:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
When you later decide that you want to insert the object, you can just have the context inserting it:
[context insertObject:unassociatedObject];
[context save:&error];
I am trying to get all objects for an entity. I execute an NSFetchRequest and the objects are returned successfully in "fault" format. My question is, how can I take it a step further and access the actual object properties in my fetch result? This seems like it should be simple, yet I am missing something. Specifically, how I do get the values out of "result" below. I have tried iterating through result, assigning the current object to an NSDictionary, then accessing the object properties via objectForKey, but that did not work. I have read through apple developer documentation, online forums, stack overflow questions with no luck. Please help! I can save data to the database. BUT How do you get the freakin data OUT of the database using core data!!??
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchReq = [[NSFetchRequest alloc] initWithEntityName:#"MyEntity"];
NSError *error = nil;
NSArray *result = [context executeFetchRequest:fetchReq error:&error];
By default objects will return as faults but as soon as you access any data it should work. For instance, if your MyEntity has a value called "name" you should be able to do
for (MyEntity *myEntity in result) {
NSLog(#"My entity name: %#", myEntity.name);
}
and you should get the results you expect.
I'm fairly new to iOS/Cocoa and I have a very fundamental question about core data. I was looking over the internet to find a appropriate answer/solution but I wasn't able to.
So the question is how do you handle uniqueness in core data? I know that core data is not a database, its something like an object graph. Lets assume we have an entity called 'User' with the attributes 'id' and 'name' and a few relations to the other entities. Now we want to update the name of a specific user (e.g. a web service gave us the id and the new name).
This was the way I have done that before:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"id == %#", anId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *user = [results lastObject];
But than I've heard this is bad practice. Is it because fetch requests with predicates are very slow? I can't imagine that this is such a big deal. As far as I know there is no other way to get a unique object rather than go over each object and checking for equality..
Would it be more efficient to fetch all objects of the entity, put them in an array and looping through it manually (instead of the fetch request)?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *resultUser;
for(User *user in results){
if([user.id isEqual:anId]) resultUser = user;
}
Please help me finding the right path and thanks.
If you have an index for the property that you're fetching (there's a checkbox in the model editor), it's definitely a lot more efficient to use an equality predicate for fetching. Actually, it's never more efficient to fetch all objects and iterate over them in memory, but an index makes the difference more significant.
You're not fetching a unique object but rather objects containing a notionally unique atrribute value.
All managed objects are unique instances but nothing in Core Data enforces that the attribute values are unique. You could in principle have an arbitrary number of unique managed objects all which had identical attribute values. Only relationships enforce a unique position in the object-graph.
There's no particular reason not to fetch a particular object that contains a particular value if that is what your app requires.
I think what you've read is warnings against trying to cram SQL-like key values into entities and then to try and link managed objects together with those keys using predicates, for example doing something like:
EntityOne{
sqlKey:string
}
EntityTwo{
sqlKey:string
}
… and then trying to relate objects of the two entities with predicates.
(anEntityOneObject.)sqlKey == anEntityTwoObject.sqlKey
… instead of just setting a relationship in the data model.
EntityOne{
two<-->EntityTwo.entityOne
}
EntityTwo{
one<-->EntityOne.two
}
… and just finding the related objects with AnEntityOneObj.two.