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?
Related
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.
I have recently moved to Realm from Coredata. In my app I am showing 50K + contacts .
The contact object is in the format:
Contact: firstName, lastName ,company
I am trying to fetch all the contacts in the Realm , and I am trying to display those contacts similar to the native contacts app in iPhone.
First I am creating the section header titles based on the contact first name:
-(NSArray *)getSectionTitleBasedOn:(NSString*)sortBy{
RLMResults *results = [self getMainDataSetFromRealm];
ContactSource *contactSource = results.firstObject;
NSMutableDictionary *nameDic = [NSMutableDictionary dictionary];
for (RealmContact *contact in contactSource.contacts){
if (contact.firstName.length>0) {
if ([sortBy isEqualToString:#"FirstName"]) {
[nameDic setObject:#"firstletter" forKey:[contact.firstName substringToIndex:1]];
}
}
}
NSLog(#"dic %#",nameDic);
return [[nameDic allKeys]sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
This gets me an array of letters which represent the title of section.
Now I am preparing the datasource for each section, so for section A, I am fetching all the contacts that begin with letter 'A'
-(void)prepareDataSource:(NSArray *)titleArr{
RLMResults *results = [self getMainDataSetFromRealm];
ContactSource *contactSource = results.firstObject;
__block NSMutableDictionary *dataSource = [NSMutableDictionary dictionary];
[titleArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *sectionHeader = obj;
RLMResults *contactResults = [contactSource.contacts objectsWhere:[NSString stringWithFormat:#"firstName BEGINSWITH '%#'",sectionHeader]];
NSMutableArray *contactRowArr = [NSMutableArray array];
for (Contact *contact in contactResults){
[contactRowArr addObject:contact];
}
[dataSource setObject:contactRowArr forKey:sectionHeader];
}];
_dataSource = [dataSource copy];
[self.tableView reloadData];
}
This works really well, but takes 3-5 seconds to load table which is fine but I am looking for ways to improve this data fetch .
Realm works on a principle of lazy-loading, where objects and their properties aren't loaded until you actually 'touch' them for the first time.
As a result, if you do any operations where you're manually iterating through all Realm objects in a results set at once, or manually copying specific objects to an array, you're going to incur a performance hit that will increase the more objects you persist in Realm.
The best way to minimize the performance hit is to try and mitigate how many times you iterate through the results sets and avoid copying objects out of the array as much as possible. RLMResults behaves like an array, so for most scenarios, you can usually just use that object instead.
In the prepareDataSource method, instead of looping through each object and passing them to that NSMutableArray, instead you could consider passing the RLMResults object itself instead.
The method getSectionTitleBasedOn: also seems quite inefficient since you're iterating through every single object in order to check if an entry with a particular first character exists. Instead, you could create an index of the alphabet, and then do a Realm query for entries that start with each letter, and then check to see if the resulting RLMResults object has a positive count (Though I'm not sure if this will actually be any faster).
But in the end, sometimes when you're doing complex sorting like this, where there's no 'clever' way to avoid iterating through each object in a database (Even Realm has to internally load each object when performing a sort), performance hits are unavoidable, in which case you should also make sure your UI has provisions to show a 'working' indicator to the user.
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 ;]
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?
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.