I am trying to create a one to many relationship with some data I have.
I have a single Project and many items, I am trying to set up the controller to save them but this is the first time I have ever used a one to many relationship and my head is about to explode.
This is what my save method looks like
- (void)writeProj:(NSArray *)recivedProData ItemsData:(NSArray *)itemsData {
// WRITE TO CORE DATA
NSManagedObjectContext *context = [self managedObjectContext];
for (NSDictionary *dict in recivedProData) {
Project *project = [NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
project.projectNumber = [dict valueForKey:#"ProjectNumber"];
project.projectDescription = [dict valueForKey:#"Description"];
// project.items = [dict valueForKey:#""]; // this is the relationship for project
}
for (NSDictionary *dict in itemsData) {
Items *items = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:self.managedObjectContext];
items.description = [dict valueForKey:#"Description"];
items.area = [dict valueForKey:#"Area"];
items.stage = [dict valueForKey:#"Stage"];
// items.project = [dict valueForKey:#""]; // this is the relationship for items
}
NSError *error = nil;
if (![__managedObjectContext save:&error]) {
NSLog(#"There was an error! %#", error);
}
else {
NSLog(#"created");
}
[Project addItemsObject:items];
[__managedObjectContext saveOnSuccess:^{
NSLog(#"You created a relationship");
} onFailure:^(NSError *error) {
NSLog(#"There was an error! %#", error);
}];
}
So I have one Project and many Items, I just dont know how to set up the keyfields so that they save into core data as one project and many items.
So hopefully my code is making sense. If someone could just help me figure out how to save it properly that would be greatly appreciated.
Just set items.project to be equal to the project NSManagedObject you just made
items.project = project;
EDIT: if you have only one project, you should move the Project* project declaration outside of the recivedProData for loop -- you are making one project for every dictionary, and you say you only have one project ever. That entire block of code makes no sense if you have only one project though -- why do you have an array of Project data, and not just one dictionary?
I want to discuss your Core Data model, the one you configured using the Core Data GUI editor. I'm curious about the plurality of the entity named "Items" from the following line of code:
Items *items = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:self.managedObjectContext];
Maybe it's just a matter of semantics. It's possible, however, that the plurality of that entity name indicates a problem in your Core Data model. I'll try to explain, but this is pretty abstract stuff.
Although an entity may have a relationship that represents a collection of things, the entity itself is not really a collection of things; the entity is always a single thing in the model and should be treated as such in your code (and in your naming schemes).
Here's how I would describe your model in words:
The Project entity is a single thing with a relationship called items. The items relationship is a collection (a set) of Item entities (i.e., a one-to-many relationship). But each Item entity is a single thing.
Does your model in the GUI editor reflect this description?
Related
I have a JSON returned by REST API to my already existing app that I am trying to fix. I am fairly new to objective C.
[
{
"Activities":"
[
{
"activityid":845,
"activityname":"Registration and networking breakfast",
"actvitydesc":"Registration and networking breakfast",
},
{
"activityid":846,
"activityname":"Plenary session: The Workforce Tsunami",
"actvitydesc":"It's Time to Rethink Talent
}
}
]
There is a core data entity Activity in my app, which contains the following attributes
Activityid activityname activitydesc
How can I insert the JSON data inside my core data entity? Is there any need to create model class to do that? Can I insert my json data directly into core data without creating model objects?
If you already have the entity called Activity you can use the NSManagedObject class to set the value for an Attribute. Try this
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:#"Activity" inManagedObjectContext:_managedObjectContext];
[managedObject setValue:[NSNumber numberWithInteger:4711] forKey:#"activityid"];
and so on...
You can also create the class by the Classgenerator of CoreData in XCode 8 there are multiple ways. Defaultly the class is generated automatically since XCode 8. If you don't like this you can disable it and generate the class manually. Just go to the CoreData Model --> Editor --> Create NSManagedObject Subclass. Note you have to deactivate the automatic code generation before. If you don't do that, you will become errors while building the project.
If the name of the JSON Attribute is equal to the name of the CoreData Attribute you can also loop over the Dictionary like this:
NSArray *wrapper = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
for(NSDictionary *dict in wrapper){
NSArray *activities = [dict objectForKey:#"Activities"];
for(NSDictionary *activity in activities){
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:#"Activity" inManagedObjectContext:_managedObjectContext];
// [managedObject setValue:[NSNumber numberWithInteger:4711] forKey:#"activityid"];
for(NSString *attributeName in activity)
[managedObject setValue:[activity objectForKey:attributeName] forKey:attributeName];
}
}
Hope that helps you...
To insert anything to core data you need to generate models. Create core data model editor (if you don't have one) and add Entities. There is lots of tutorials on the web how to do it.
I'm fetching code & desc from web by calling an API. Then loading it into tableView and based on multiple selection I'm saving the selected values into two arrays i.e. selectedCode and selectedCodeDesc. My Entity is:
So I want to [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error){ but don't know how. I know this much:
- (IBAction)confirmPressed:(id)sender {
NSLog(#"Selected Are: %# - %#",selectedDX,selectedDesc);
for (NSString *code in selectedDX) {
if (!_dxToAddEdit) {
self.dxToAddEdit = [MainCode MR_createEntity];
}
[self.dxToAddEdit setCode:code];
[self.dxToAddEdit setCodeDescription:#""]; //what to give here
[self.dxToAddEdit setSuperBill:_forSuperBill];
}
//after this I'm calling the saveToPersistent
So what to give at setCodeDescription?
If I understood correctly and based on your description and example of code you can do the following:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
// Sorry, I renamed selectedCode to selectedCodes and selectedCodeDesc to selectedCodeDescriptions for readability.
// Not sure whether selectedDX is actually selectedCodes.
for (NSInteger i=0; i<selectedCodes.count; ++i) {
NSString *code = selectedCodes[i];
NSString *description = selectedCodeDescriptions[i];
Diagnoses *newDiagnose = [Diagnoses MR_createEntityInContext:defaultContext];
newDiagnose.code = code;
newDiagnose.codeDescription = description;
newDiagnose.superBill = _forSuperBill;
}
[defaultContext MR_saveToPersistentStoreAndWait];
Actually, I would not save the response into two separated arrays. Because of:
Your code becomes difficult to read
Imagine that the model will change and instead of two properties it will contain 4. You will have to create additional arrays.
I would recommend you to parse the response directly into the managed objects. Of course, you may not save them into persistent storage just populate your table view.
I highly recommend you to read these tutorials about Core Data. It will give you insight how to work with Magical Record library. Although, the library simplifies a lot of work it would be better to know what is under the hood ;]
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 making the mental switch from a lifetime of SQL to CoreData and it's not without a few hiccups. The thing that's kicking me at the moment is creating a self-referencing object. Allow me to explain.
Using SQL terminology, I have a table that contains the steps in a process. To keep it simple, I'll say the table holds two pieces of information - the name of the step and the step that comes after it (which may or may not be the next record in the table). All steps in the process are stored in a single table. Each step will ALWAYS have one step that comes after it. But not all steps have a step that comes before it.
In other words, it should look something like this:
(source: justinwhitney.com)
In the SQL world, I would make a single table with an identity field, the name, and a foreign key referencing its own identity field (I guess that would be a domestic key?), thusly:
(source: justinwhitney.com)
However, with relationships, there is no such thing as an identity field. Nor is there a select query I can create to pull the info I need.
So what is the best approach for creating an Entity that does the same thing? I tried creating a relationship that inverted to itself and that turned out to be a hard to debug disaster. What are the other options?
Thank you!
Create a relationship "nextStep" from the entity to itself. Then you can just
do something like
// Create first thing:
Thing *thingA = [NSEntityDescription insertNewObjectForEntityForName:#"Thing" inManagedObjectContext:context];
thingA.name = #"...";
// Create second thing:
Thing *thingB = [NSEntityDescription insertNewObjectForEntityForName:#"Thing" inManagedObjectContext:context];
thingB.name = #"...";
// Establish the relationship between these two objects:
thingA.nextStep = thingB;
"nextStep" should also have an "inverse relationship". Since two or more objects
can have the same successor (as in your case, where both "C" and "D" point to "E"),
the inverse relationship would be a "to-many" relationship, and could be called
"previousSteps" or similar.
In the Core Date model editor, this would look like:
Ah! You provided the vital clue, Martin.
I tried the code sample, but that didn't quite work. It ended up creating duplicates of everything because both thingA and thingB got inserted into the table. However, the diagram actually gave me what I think might be the key. Previously I had tried assigning nextStep as its own inverse relationship, which was just bonkers. But just adding previousSteps and setting that to Many while nextStep was set to One seems to have led to the solution.
Here's what I ended up creating for the relationships:
(source: justinwhitney.com)
Here is the plist I used to populate the Steps entity (intended to be run on first-time use or when the database is reset):
(source: justinwhitney.com)
And now here is the entity population routine. This is what was tripping me up yesterday:
- (IBAction)populateSteps:(UIButton *)sender {
NSString *responseString;
BOOL isStepsPopulated = [[UserPrefs loadUserData:#"didPopulateSteps"] boolValue];
if (!isStepsPopulated) {
NSDictionary *stepDict = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"DefaultSteps" ofType:#"plist"]];
NSArray *stepArray = [stepDict objectForKey:#"Steps"];
//1
__block NSMutableDictionary *stepObjectDict = [[NSMutableDictionary alloc] initWithCapacity:[stepArray count]];
//2
[stepArray enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
//3
Steps *thisStep = [NSEntityDescription insertNewObjectForEntityForName:#"Steps" inManagedObjectContext:self.managedContext];
thisStep.stepName = [dict objectForKey:#"StepName"];
//4
[stepObjectDict setObject:thisStep forKey:thisStep.stepName];
}];
//5
[stepArray enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
Steps *thisStep = [stepObjectDict objectForKey:[dict objectForKey:#"StepName"]];
Steps *nextStep = [stepObjectDict objectForKey:[dict objectForKey:#"NextStep"]];
thisStep.nextStep = nextStep;
}];
NSError *error = nil;
[self.managedContext save:&error];
if (error) {
responseString = [NSString stringWithFormat:#"Error populating Steps: %#", error.description];
} else {
responseString = #"Steps have been populated.";
[UserPrefs saveUserData:[NSNumber numberWithBool:TRUE] forKey:#"didPopulateSteps"];
}
} else {
responseString = #"Steps already populated.";
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Populate Steps" message:responseString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
Here's the key. At (1) create an NSMutableDictionary to hold the results of the iteration, using the stepName as the Key so it can be referred to later on.
While (2) enumerating through the plist contents, (3) create the first NSEntityDescription just as you did. But instead of creating the second one, (4) add it to the dictionary.
Once the initial enumeration is done, (5) go back through a second time. But this time, create the relationships by referring to the original objects themselves. Make sense?
After that, save the context as normal. The result, in this case, is 5 records, each referencing another record in the same entity, with no conflicts or duplicates.
(source: justinwhitney.com)
Now the important question: how do I mark this as answered? Who gets the checkmark?
I have an entity which looks like this:
Entityname = Country
with the attributes "city" and "person".
Country *country = [NSEntityDescription insertNewObjectForEntityForName:#"Country" inManagedObjectContext:context];
country.city = #"New York";
country.person = #"Doug";
country.person = #"Carry";
country.person = #"Arthur";
I want to save more then one people in that City.
I am using the code posted above, but only the last person is saved.
How I can save more then one people in CoreData?
Hope you can help me.
An approach for solving your problem would be:
Create 3 Entities: Country, City and Person
Setup the properties for your entities (e.g. Country.name, City.name, Person.name etc) using the graphical tool of XCode
Setup the relations between your Entities. You need a one-to-many from Country -> City [call it cities] and a one-to-many from City -> Person [call it persons] (See Apple's documentation regarding this subject). Keep in mind that you will need to set the inverse relations as well.
That's where all the fun begins... Choose Editor > Create NSManagedObject subclass. Xcode then will generate the files based on your model. Now if you look at the header files, you should see among the generated methods something similar to this:
...
- (void)addPersonObject:(Person *)value;
- (void)removePersonObject:(Person *)value;
- (void)addPersons:(NSSet *)value;
- (void)removePersons:(NSSet *)value;
...
From this point is quite obvious to figure out how to add multiple objects :)
I know that all this may seem hard at first but once you get yourself into this you will really be able to manage complex object graphs easy and efficiently.
I hope that this information will set you on the right track!
You will need to create an array with all the people you need to save to the core data model.
Try this code. Hope this will help you
yourArray = [[NSMutableArray alloc]initWIthObjects:#"Doug",#"Carry",#"Arthur"];
for(int i = 0; i < [yourArray count]; i++)
{
NSManagedObjectContext *context = [self managedObjectContext];
countryObject=[NSEntityDescription
insertNewObjectForEntityForName:#"Country"
inManagedObjectContext:context];
countryObject.city = #"New york";
countryObject.people = [NSString stringWithFormat:#"%#",[yourArray objectAtIndex:i]];
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
UPDATE:
AFTER YOU EXPLAINED THAT YOU DONT NEED THREE DIFFERENT INSTANCES, then
This can be done by creating a separate entity for City and People, then make a relationship between them as to-many relationship. So that you can achieve like that.