Platform
iOS 10, Xcode 8.3.3
Background
I have built a Notes application that takes advantage of Core Data and I'd like to use this app in my next application, which will also use Core Data. For simplicity, lets call my next application, "ListApp", and my notes application, "NotesApp". This ListApp has list items each of which can have one or more notes.
Here's what I've done so far:
Removed all unnecessary files from the NotesApp and compiled a "NotesApp Framework".
Linked the NotesApp Framework to the ListApp.
Designed the Core Data Model for ListApp. Specifically, I created an entity called "ListItem" and an entity called "Note". The ListItem has a to-many relationship with the Note (one list item can have multiple notes). The Note entity contains a "noteID" field to reference the note in the NotesApp model and, of course, the inverse relationship.
Problem
I need to form a "relationship" between an entity in the ListApp model and an entity in the NotesApp model.
I've researched configurations and that seems to be more for storing objects in the same model to different persistent stores unless there's something I'm missing. So, that doesn't help.
Then, I found that fetched properties can be used to form weak relationships between multiple stores. So, that doesn't help either.
Next, I found in the documentation that there's a method called NSManagedObjectModel.mergedModel(from:) so I'm assuming this is possible. Or maybe that's only for migration?
That's where I'm stuck.
Reason
I'd rather not redesign everything in the NotesApp model in to the ListApp model. I prefer to keep everything separate.
Questions
Is there a way to form a relationship between two entities in different models? Should I just add a function in the ListItem entity class to fetch the notes in the NotesApp model manually? Am I even going down the right path or is there a better option?
NOTE: What I mean by "relationship" is the ability to call on a property in the ListItem entity to fetch the notes and somehow "relate" specific notes to a specific ListItem.
P.S. If you know of any pitfalls, have any general advice, or know of any reading material please feel free to let me know.
Also, I've been researching this topic for a couple of hours and I can't seem to find anything about it. I'm assuming that's either because it's not possible, it's a terrible practice, or I'm not using the right keywords.
If anyone needs any more information feel free to let me know!
I think you're saying your bundle will have one model that contains the List entity and another that contains the Note entity. You can merge and tweak managed object models, as you suggested, and use the resulting managed object model which you have in code.
If you're creating your Core Data stack in code (that is, you are not using the new NSPersistentContainer), it is easy to splice in a custom managed object model.
If you are using NSPersistentContainer, you'd have to subclass it and override managedObjectModel(). I can't find any documentation saying you can't do that, but I wouldn't bet on that.
If you're document-based, overriding UIDocument's managedObjectModel should work.
To create your custom managed object model, merge your models using NSManagedObjectModel.mergedModel(from:). Then, get the Note and List entity decriptions, get the properties of each, mutate, add your new relationships, then set them back into the model. You would only do this on the first run; cache your custom managed model to a ivar for subsequent runs.
Hmmmm. What I've just described, essentially tearing apart, tweaking and reassembling that managed object model, is going to be quite a few lines of code. If this is a really just a simple "notes and lists" app, and if these are the only these two entities, it would probably be less code to ditch those .mom files and create the whole managed object model from scratch, in code. It's not that hard. Put on your Objective-C glasses and look at the managedObjectModel() function in main.m of Apple's old Core Data Utility sample project.
Alright, so turns out I had a slight misunderstanding of the Core Data Stack but, this is an extremely simple task. I was able to get this to work very easily based on some research and #Jerry Krinock's answer.
Create a framework containing the needed files from the NotesApp.
Link the framework to the ListApp.
Grab mutable references to the ListApp and NotesApp NSManagedObjectModel.
Programmatically add a NSRelationshipDescription between the ListItem entity in the ListApp Model and the Note entity in the NotesApp Model (and vice versa for the inverse).
Create a NSManagedObjectModel by merging the ListApp and NotesApp models.
NOTE: As #Jerry Krinock mentioned this only needs to be done once since we are merging the two models together and storing them in the same persistent store. This is the same as doing it through the CoreData Model Builder UI except programmatically since it doesn't support referencing entities from separate models (at least not that I know of or could find).
References:
Core Data Programming Guide
Core Data stack
Universal Cocoa Touch Frameworks for iOS8 – (Remix)
Adding relationships in NSManagedObjectModel to programmatically created NSEntityDescription
Objective-C:
NSManagedObjectModel * listModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self listModelURL]] mutableCopy];
NSManagedObjectModel * notesModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self notesModelURL]] mutableCopy];
NSEntityDescription * listEntity = [listModel.entitiesByName objectForKey:NSStringFromClass([JBListItemMO class])];
// The framework name is prepended to the class name. Remove it before getting the note's entityDescription.
NSString * noteClassName = [NSStringFromClass([JPSNoteMO class]) componentsSeparatedByString:#"."].lastObject;
NSEntityDescription * noteEntity = [notesModel.entitiesByName objectForKey:noteClassName];
NSRelationshipDescription * whichListRelationship = [[NSRelationshipDescription alloc] init];
whichListRelationship.minCount = 0;
whichListRelationship.maxCount = 1;
whichListRelationship.optional = NO;
whichListRelationship.name = #"whichList";
whichListRelationship.destinationEntity = listEntity;
whichListRelationship.deleteRule = NSNullifyDeleteRule;
NSRelationshipDescription * notesRelationship = [[NSRelationshipDescription alloc] init];
notesRelationship.ordered = NO;
notesRelationship.maxCount = 0;
notesRelationship.minCount = 0;
notesRelationship.optional = YES;
notesRelationship.name = #"notes";
notesRelationship.destinationEntity = noteEntity;
notesRelationship.deleteRule = NSCascadeDeleteRule;
notesRelationship.inverseRelationship = whichListRelationship;
whichListRelationship.inverseRelationship = notesRelationship;
listEntity.properties = [listEntity.properties arrayByAddingObject: notesRelationship];
noteEntity.properties = [noteEntity.properties arrayByAddingObject: whichListRelationship];
self.managedObjectModel = [NSManagedObjectModel modelByMergingModels:#[listModel, notesModel]];
I'll post the Swift 3 code when I've finished converting my CoreDataStack class.
Related
I am new in app development and I have really big issues with my split view core data iPad application. Even though I made sure that I have built all core data structure very well, I have problems when I pass objects between view and saving them via core data. Basically my problem is in this way:
I have peopleviewcontroller where I list all people in list. And I have addpersonviewcontroller where I create new person and save it to coredata. However, even though saving new object in new class seems successful in save-error structure, no data is written to core data. I debuted the code and figured out that managedjectcontext of created person is always null. Hence, it is not written to core data.
I have spent really lots and lots of time for solution, including days of reading stackverflow, but no solution.
Any help about this issue is deeply appreciated. Thabk you in advance.
Your Person is a subclass of NSManagedObject. In your AddPersonViewController, you never created the object. You have to use insert that entity into core data before you save it.
I recommend having some store/repository that handles all of this.
For instance, I have this in my code:
- (NSManagedObject *)createManagedObject:(NSString *)entityName
{
NSManagedObject *objectCreated = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.managedObjectContext];
[self saveContext];
return objectCreated;
}
I then call it with the entity type and it returns a NSManagedObject. You can then cast it to your Person class with
Person *addPerson = (Person *)managedObject;
Now you can make your changes and save it.
A NSManagedObject must be created by the store before it can be saved. You cannot just new it up and expect it to work.
I have been at this single task for several days trying to get the relationships between core data entities working. I have achieved this but now I need to change it so that the new attribute value has its relationship added to an existing object. It is a 1 - to - many database.
I am not sure how to add a relationship to a object that already exists. So in the new object that is getting added to RoutineDetail, how would I create the relationship to the object that already exists in the routine Entity?
I have looked at several examples all showing how to add relationships to newly added objects but I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
The value of Routines is held in a string called RoutineText
rout is the NSmangedObject for the entity Routines
routDet is the NSmanagedObject for the entity RoutinesDetails
I have left the commented out code that allows me to add a relationship when both new objects are created.
This is the last thing I have to do in my project but it is driving me insane. I will be eternally grateful for the fix here. Any advice will be appreciated as this is the best knowledge portal there is. Thank You.
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new device
ExcerciseInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
//rout = [NSEntityDescription insertNewObjectForEntityForName:#"Routines" inManagedObjectContext:context];
routdet = [NSEntityDescription insertNewObjectForEntityForName:#"RoutinesDetails" inManagedObjectContext:context];
//Add attribute values
//[rout setValue: RoutineText forKey:#"routinename"];
[routdet setValue: info.name forKey:#"image"];
//Create Relationship
[rout addRoutinedetObject:routdet];
Your main problem statement is, I think, here:
I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
I presume your data model looks like this:
Routine <----> RoutineDetail
i.e. every one routine has one routine detail (one-to-one relationship).
But this does not really make any sense. You could simply include the attributes of RoutineDetail in the Routine entity.
Instead of
desiredValue = routineDetail.routine.value;
you would simply have
desiredValue = routineDetail.value;
Also, please note that your code has a number of problems. The first line is completely unnecessary, just use self.managedObjectContext. Additionally, against the convention you are using Capital initials for variables. Those should be reserved for class names. Your method to add the relationship also does not look right.
You can add a relationship like this, without a method call:
routineObject.detail = detailObject;
Currently I have two entities in my model; Module and Level. They have one to many relationship e.g. a level can have a lot of modules. How can can I insert data so that they are related one to another so that I can say that module has specific year. I need this so that when I delete module it also deletes a year.
Module<<---->Level
If I just add objects separately it works fine, but I add last line of code application crashes it says that entity Module is not key value coding compliant for the key level_number which is the name of relationship on the Module side.
NSManagedObject *newModule;
newModule = [NSEntityDescription
insertNewObjectForEntityForName:#"Module"
inManagedObjectContext:context];
[newModule setValue:textModuelTitle.text forKey:#"name"];
[newModule setValue:value forKey:#"credit"];
NSManagedObject *newLevel;
newLevel = [NSEntityDescription
insertNewObjectForEntityForName:#"Level"
inManagedObjectContext:context];
[newLevel setValue:year forKey:#"value"];
[[newModule mutableSetValueForKey:#"level_number"] addObject:newLevel];
When you have one to many relationship, set the values from one side and Core Data would take care of the other side.
Since a level has one to one relationship to module. Set module to a level and then save the context.
Could you post your entire crash message.
Double check your relationship is setUp correctly. Sometimes the simulator may hold on to the old coreData structure. Try deleting the application from it and re run.
I would like to add attributes programmatically to an entity during runtime of my app.
Is this something you would recommend doing or can this lead to issues?
How would I need to combine NSAttributeDescription and NSEntityDescription? I am familiar with creating models using Xcode, but did not do it using NSEntityDescription yet.
It's theoretically possible, but doesn't appear very practical.
You can modify the NSManagedObjectModel programmatically, as well as NSEntityDescription. Note that -setEntities: (NSManagedObjectModel) and -setProperties: (NSEntityDescription) both trigger exceptions if you modify a model that has been instantiated. So you can't modify your existing model's structure. You'd have to create a new one and copy all of your data from the old Core Data stack to the new one based on your new model..
Using NSMutableDictionary is a much saner approach.
This is an article talking about this in great detail. Hope it helps.
I would not do this. If the store becomes incompatible with your model it will just crash. Is this risk really worth the benefit you are trying to create?
I have found that it makes sense to create more (even many more) attributes upfront just "to be on the safe side". The overhead of unused attributes is really minimal, but you get the flexibility of easily adding information to your objects "on the fly".
As pointed out in the comments, one good way to implement that is using a separate entity for attributes and adding them as to-many relationships.
I just used nearly the same technique here: EPPZQueuedObject.h
Although, I think mutate the entity architecture during runtime could lead to incompatiblity issues (an exception actually), when the stored SQLite data won't fit for your initial entities at startup.
So this generic object EPPZQueuedObject is an object of two attributes at all, so I had no intention to use a separate model file only for this purpose. But this structure is not mutating during runtime.
#implementation EPPZQueuedObject
#dynamic creationDate;
#dynamic archivedObject;
+(NSEntityDescription*)entityDescription
{
//Describe EPPZQueuedObject.
NSEntityDescription *entityDescription = [NSEntityDescription new];
entityDescription.name = EPPZQueuedObjectEntityName;
entityDescription.managedObjectClassName = NSStringFromClass(self);
//Describe creationDate.
NSAttributeDescription *creationDateDescription = [NSAttributeDescription new];
creationDateDescription.name = #"creationDate";
creationDateDescription.attributeType = NSDateAttributeType;
creationDateDescription.attributeValueClassName = #"NSDate";
creationDateDescription.defaultValue = nil;
//Describe archivedObject.
NSAttributeDescription *archivedObjectDescription = [NSAttributeDescription new];
archivedObjectDescription.name = #"archivedObject";
archivedObjectDescription.attributeType = NSBinaryDataAttributeType;
archivedObjectDescription.attributeValueClassName = #"NSData";
archivedObjectDescription.defaultValue = nil;
//Add attributes.
entityDescription.properties = #[ creationDateDescription, archivedObjectDescription ];
//Voila.
return entityDescription;
}
#end
More details in the corresponding article: http://eppz.eu/blog/simple-core-data-sample/
I'm working on something similar and I'm thinking about creating a new core data class called "Properties", so I can set my core data objects to have a "relationship to many Properties". Each Property would have core data string-type attributes: "attribute", "type" and "value".
I think that should give enough flexibility to add properties to a core data object in the fly. If I happen to implement this, I will post it here
How to get list of existing entities (tables) for a particular schema (Managed Object Model) in core data. I just started implementing core data concept and stuck with these points.
Something like:
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'dbName';
Thanks
You should read through Apple's Core Data Programming Guide. To get the entities for a particular NSManagedObjectModel, you would use one of the following (this assumes you have an NSManagedObjectModel named objectModel):
NSArray *myEntities = [objectModel entities];
// Array of all entities in the model
or
NSDictionary *myEntities = [objectModel entitiesByName];
// Dictionary of entities in the model, with the entity names as keys
You can read more in the NSManagedObjectModel Class Reference.
It appears you're coming from a SQL background (as I was). There are a number of concepts in Core Data that are different - sometimes for the better, once you understand them, sometimes requiring more work than a simple SQL statement you may be used to. I think it's important to approach Core Data without SQL "baggage" and treat it as if you're learning how to use a database for the first time - this will help avoid frustration.
In Swift it would be:
let model: NSManagedObjectModel
let entities: [NSEntityDescription] = model.entities
// or
let entitiesByName: [String: NSEntityDescription] = model.entitiesByName
and to get a list of names
let enititesNames: [String] = entities.compactMap(\.name)