I have a model with 2 entities: RealEstate and Container
Containers object are already saved onto the persistent store with the following hierarchy:
Container 1
Container 2
Container 3
Container 4
Container 5
Container 6
Each container has a RealEstate owner (in a given hierarchy the realEstate is always the same)
Now I would like create a copy of this hierarchy changing for each containers the owner realEstate.
After some tests it seems to me that it's not a trivial problem.
This is a simplified scheme of the model:
RealEstate (entity)
-------------------
name (string attribute)
containers (relation)
Container (entity)
------------------
level (int attribute)
name (string attribute)
parent (self relation to another container)
subcontainers (relation - set of containers)
realEstate (relation)
Basically each containers has a subcontainers relation with self, and a parent relation so for example
Container 1 has no parent but subcontainers=[container 2, container 3] etc...
I have 2 questions:
if I want to copy a single attribute (NSNumber) should I copy the attribute before assign it to the copied container with something like [newContainer setLevel:[[container level] copy]] because NSNumber is actually a pointer, or it's ok to assign [newContainer setLevel:[container level]] ??
how to copy a relation?? I cannot simply copy the subcontainers with [newContainer setSubcontainers:[container subcontainers]] !!
This is what I'm doing:
- (void)copyContainersFromRealEstate:(RealEstate *)sourceRealEstate toRealEstate:(RealEstate *)destinationRealEstate {
// read all RealEstate Containers to copy
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"realEstate == %#", sourceRealEstate];
[request setPredicate:predicate];
[request setEntity: [ NSEntityDescription entityForName:#"Container"
inManagedObjectContext:destinationRealEstate.managedObjectContext] ];
NSError *error;
NSArray *results = [destinationRealEstate.managedObjectContext executeFetchRequest:request
error:&error];
if (results == nil) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[request release];
// *******************************************************
// copy each caontainer changing the real estate
Container *newContainer;
for (Container *container in results) {
newContainer = (Container *)[NSEntityDescription insertNewObjectForEntityForName:#"Container"
inManagedObjectContext:destinationRealEstate.managedObjectContext];
[newContainer setRealEstate:destinationRealEstate];
[newContainer setLevel:[container level]];
[newContainer setSubcontainers:[container subcontainers]]; // WRONG
[newContainer setName:[container name]];
[newContainer setParent:[container parent]]; // WRONG
}
// *******************************************************
}
Any help will be appreciated.
Thanks
if I want to copy a single attribute (NSNumber) should I copy the attribute before assign it to the copied container with something like [newContainer setLevel:[[container level] copy]] because NSNumber is actually a pointer, or it's ok to assign [newContainer setLevel:[container level]] ??
Set the value onto the new object, it will handle it even though it is a pointer because Core Data will be writing the value down into the database so it will be translating it to a primitive at some point and "break" that pointer relationship that you are concerned about.
how to copy a relation?? I cannot simply copy the subcontainers with [newContainer setSubcontainers:[container subcontainers]] !!
Just set the objects and Core Data will do the right thing and construct the new relationships correctly. When you pass the NSSet to the new parent object, Core Data will iterate through the objects in the set and create new relationship connections for each one of them to the new parent object.
Thank you for your reply.
I'm sorry if write as a new answer but is the only way to post a screenshot.
Just set the objects and Core Data will do the right thing and construct the new relationships correctly. When you pass the NSSet to the new parent object, Core Data will iterate through the objects in the set and create new relationship connections for each one of them to the new parent object.
I understand that coredata will create new relationship connections to the new parent object and automatically make necessary changes, especially if the relation is modeled in both directions, but in my case something goes wrong, maybe due how I designed my relations.
Reviewing my code I see that [newContainer setParent:[container parent]]; is not necessary because "parent" and "subcontainers" are inverse relationships, so setting "subcontainers" will automatically set "parent". But even with this change there is something wrong.
To simplify the problem I just made this test:
let say that I just want to copy a subtree and not whole tree, for example Container 5 with its child Container 6 (see hierarchy from my first answer)
first checks that the relations is right from the source: if I print out sourceRealEstate Container 5 I see: Container5.parent=Container4, Container5.subcontainers=[Container 6]. So it seems that everything is ok into the copy source
than start my copy procedure... after that I try to print out in the same way the copied object and I see: Container5.parent=nil (WRONG), Container5.subcontainers=[Container 6] (RIGHT). Than I recheck source Container and I see: *Container 5.parent=Container 4 (RIGHT), Container5.subcontainer=empty (WRONG)
Conclusions: the subcontainers are moved and not copied
This is my model in more detail:
http://www.freeimagehosting.net/uploads/9aae9b9ac8.png
Container.parent relationship:
http://www.freeimagehosting.net/uploads/7f5c6592ef.png
Container.subcontainers relationship:
http://www.freeimagehosting.net/uploads/bff3e1abe8.png
Related
I have a to-many relationship, e.g. A student has many classes.
Why is it when I run the following code _classes does NOT have the new Cls object in it? Yet when I close the app and reopen it does.
And as a follow up question, How would I get the new list of classes right after adding adding a new one?
NSMutableSet* classes = [student mutableSetValueForKey:#"classes"];
NSManagedObject* cls = [NSEntityDescription insertNewObjectForEntityForName:#"Cls" inManagedObjectContext:context];
[cls setValue:name forKey:#"name"];
[student addClassesObject:cls];
NSError* err;
[context save:&err];
NSMutableSet* _classes = [student mutableSetValueForKey:#"classes"];
And this is what my addClassesObject: operation looks like (auto generated by XCode)
- (void)addClassesObject:(Cls*)cls
{
}
Thank you!
You can't add objects to the set directly. You need to use Core Data generated accessors or your own custom ones. Look in your .h file and you should see something along the lines of
-(void)addClsObject:(Cls *)theObject;
And
-(void)addClsObjects:(NSSet *)set;
So for your particular case, you would do the following once you have your Cls object:
[student addClassesObject:cls];
Then perform you save and you should be able to retrieve the classes set. FYI, calling this method and the relatd NSSet relatd method also sets up the reverse relationship for you.
Good luck
T
My data model contains two entities: Author and Book with a one to many relationship (one author may write several books).
Let's say that there are only two books and two authors in DB as follows:
Book A is assigned to Author X
Book B is assigned to Author Y
Assuming following change is applied:
Book B is assigned to a new Author Z.
Result:
Author Y exists in DB but points to no book.
My question: is it possible to configure the data model so objects like Author Y will be automatically deleted when they are not referenced by any book?
Check out "delete propagation". It's there to solve exactly that problem.
If that doesn't do exactly what you want / need: You can override - (void)prepareForDeletion on the Book entity and at that point check for any Authors that are registered with the context and have pending changes (since their inverse will have changed) and have no books:
{
// ...
[[NSNotificationCenter defaultNotificationCenter] addObserver:self selector:#selector(deleteOrphanedAuthors:) name:NSManagedObjectContext object:moc];
// ...
}
- (void)deleteOrphanedAuthors:(NSNotification *)note;
{
NSManagedObjectContext *moc = [note object];
NSManagedObjectModel *mom = [[moc persistentStoreCoordinator] managedObjectModel];
NSEntityDescription *authorEntity = [[mom entitiesByName] objectForKey:#"Author"];
for (NSManagedObject *author in [moc updatedObjects]) {
if ([author entity] == authorEntity) {
if (![author hasFaultForRelationshipNamed:#"books"] && ([[author books] count] == 0)) {
[moc deleteObject:author];
}
}
}
}
Note: You can not pass nil as the object (i.e. context) to observe, since frameworks you use, might have their own context, and you do not want to mess with them.
Also, note how this code is careful not to touch the author object if it's a fault. If a book is deleted, Core Data will change the corresponding author objects' inverse relationships, hence fault in that relationship, such that it is no longer a fault. And the code will only operate on those objects.
You will need to determine "orphaned" books manually.
When you update the Author relationship you could check the old Author's books relationship to see if it still has any books.
Alternatively you could use notifications to determine when the NSManagedObjectContext changes: NSManagedObjectContextObjectsDidChangeNotification. If you register for this notification you can check for a number of changes to Author objects. Have a look at that specific notification in the docs.
I have a super entity in core-data called element and has two sub entity called (IsBoolean,IsGrade) i try to access these sub-entity attribute from below code.I need your help about this issue
--------------------- Core data structure --------------------
Super Entity [Element->elmentID]
Sub Entities [IsBoolean->value] + [IsGrade->value]
---------------------- brief code ----------------------------
NSFetchRequest *formRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *formEntity = [NSEntityDescription entityForName:#"Element" inManagedObjectContext:ManagedObjectContext];
NSSortDescriptor *formDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"elementID" ascending:YES];
NSPredicate *formPredicate = [NSPredicate predicateWithFormat:#"elementID==%d",2];
[formRequest setPredicate:formPredicate];
[formRequest setEntity:formEntity];
[formRequest setSortDescriptors:[NSArray arrayWithObject:formDescriptor]];
[formRequest setIncludesSubentities:YES];
resultController_= [[NSFetchedResultsController alloc] initWithFetchRequest:formRequest managedObjectContext:ManagedObjectContext sectionNameKeyPath:nil cacheName:nil ];
resultController_.delegate =self;
for (Element *elementData in resultController_) {
// I can access super entity attribute
NSlog(#"%#",elementData.elementID);
// Here i can't access sub entity attribute from super entity
NSLog(#"%#",elementData.value);
}
Find the following screenshot for datamodel
Please note that sub-entities do not have to be sub-classes. The class hierarchy and entity hierarchy do not have to match. In most cases it will make sense to have them match, but there are use cases where that's not the case. In fact, not having them match may give you a lot of flexibility.
You can have an entity book* with corresponding **Book class, and an entity author with a corresponding Author class. In this case they would not have a common super-entity. But the classes may very well have a common super-class that defines and implements e.g. #property NSString *name; and #property UIImage *image; and related methods.
Update based on amended question
You will not have any instances of your sub-entities unless you have added managed objects to your context that are of the sub-entity.
Each sub-entity is equivalent to a subclass of your Element entity - so it will have the element attributes, plus the new attributes you have defined in the sub-entity. It will also inherit in class terms if you have made custom NSManagedObject subclasses.
To talk about accessing the properties of a sub-entity from within a super entity is missing the point.
To add instances of the sub-entity, you would just insert entities as you would any other managed object, using the sub-entity name.
Original answer, 90% of which is still valid
You're not trying to access them from a super entity, you're accessing them from whatever is doing your fetch request. In that case, if you check that your returned objects are of the appropriate class or have the relevant attributes, then yes.
But from within a super-entity, no, that doesn't make any sense. It's the same as class inheritance - you couldn't access .text from within a UIView just because UILabel is a subclass of it. But you could have a list of objects that were UIViews, check if one of them was in fact a UILabel, then access it's .text property.
For a fetch request returning a mix of entities and sub-entities, you'd do something like
if ([elementData.entity.name isEqualToString:#"IsBoolean"]) // means it is the isBoolean sub-entity
NSLog(#"%#",elementData.value);
Slightly better, you would get the attributes dictionary:
NSDictionary *attributes = elementData.entity.attributesByName;
if ([attributes objectForKey:#"value"]) // Means there is an attribute called "value"
NSLog(#"%#",elementData.value);
With the latter case the dictionary is full of NSAttributeDescription objects which tell you what kind of attribute you are dealing with. See the various core data class references (NSManagedObject, NSEntityDescription and NSAttributeDescription).
Suppose I have an entity MeetingNote with standard attributes plus a one to many relationship to another entity Tag.
Out of MeetingNote instances, I want to create instances of another entity called Task but only in case meetingNote.tag.name == 'task' is TRUE.
What do you think would be the correct way of modelling the relationship between Task and MeetingNote? Or should there be a relationship at all and I ought to use a fetched property with the appropriate predicate instead?
Firstly, the true purpose of Core Data is not persistence but rather to create the model layer of a Model-View-Controller design app. That means that Core Data is really a model/simulation API first and a persistence API second. A Core Data data model, therefore, should accurately represent the attributes of real-world objects, conditions or events and the relationships between them.
So, when you set down to build a data model, you should forget about the UI, the data source or any other implementation details and simply try to create a model that mirrors the real-world objects, conditions or events the app deals with.
Secondly, while a data model deals with how entities are related, it doesn't deal with the logic of why the entities are related. That logic is belongs in code somewhere often in the custom NSManagedObject subclasses for the entities. In this case, the how of the entities relationships is that the MeetingNote entity is related to both Task and Tags. The why is that there should be a relationship between any particular MeetingNote object and anyTask object only if the MeetingNote object has a relationship to a Tag object with the name of task.
So, your basic data model should look like this:
MeetingNote{
title:string
date:date
tags<<-->>Tag.meetingNotes
tasks<-->>Task.meetingNote
}
Task{
name:string
meetingNote<<-->MeetingNote.tasks
}
Tag{
name:string
meetingNotes<<-->>MeetingNote.tags
}
Now the question becomes one of where to stick the custom logic for the why. The most logically simple way would be to create a custom accessor for MeetingNote.tags property that checks if name of a tag being added or removed to an MeetingNote instance equals task and if so, adding or removing a Task object from the instance's MeetingNote.tasks relationship.
However, that has an obvious performance penalty of having to check every tag added or removed. A better solution would be to add the custom to only one point that is called only when the exact condition of MeetingNote.tags.name' contains a value oftask`.
Let's assume you have the following constraints:
A MeetingNote object cannot have a related Task object without also having a Tag object with name=="task".
If the MeetingNote object does have a Tag object with name=="task" it must have at least one related Task object.
If a MeetingNote object looses its relationship to a Tag object with name=="task", then it loses all its task.
It is immediately obvious at this point that `Tag object with name=="task" is a special object with behaviors different from other tags. This justifies and requires that it have its own entity and subclass so we would add to the data model:
TaskTag:Tag{
}
Since the TaskTag entity inherits from the Tag entity it can automatically inherits the in the Tag.meetingNotes relationship so it will behave as a Tag object from the perspective of any MeetinNote objects.
Then in the TaskTag NSManagedObject subclass we would add the following code:
-(NSString *) name {
// the name of a TaskTag is always "task"
// you should set the defalut value in the data model to "task" as well.
return #"task";
}
-(void) setName:(NSString *)name{
return; // the name can never be changed
}
- (void)addMeetingNotesObject:(MeetingNote *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:#"meetingNotes"] addObject:value];
// If the meeting object does not an existing task, add one
if ([value.tasks count]==0 ) {
Task *t=[NSEntityDescription insertNewObjectForEntityForName:#"Task" inManagedObjectContext:self.managedObjectContext];
t.meetingNote=value;
}
[self didChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[changedObjects release];
}
- (void)removeMeetingNotesObject:(MeetingNote *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:#"meetingNotes"] removeObject:value];
// A MeetingNote object cannot have any task without a taskTag so remove all task objects
if ([value.tasks count]!=0 ) {
[value removeTasks:value.tasks]; // removes all tasks from meeting notes
}
[self didChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[changedObjects release];
}
- (void)addMeetingNotes:(NSSet *)value {
[self willChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
[[self primitiveValueForKey:#"meetingNotes"] unionSet:value];
Task *newTask;
// same as addMeetingNotesObject:
for (MeetingNote *meetNote in value) {
if ([meetNote.tasks count]==0 ) {
newTask=[NSEntityDescription insertNewObjectForEntityForName:#"Task" inManagedObjectContext:self.managedObjectContext];
newTask.meetingNote=value;
}
}
[self didChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
}
- (void)removeMeetingNotes:(NSSet *)value {
[self willChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
[[self primitiveValueForKey:#"meetingNotes"] minusSet:value];
//removeMeetingNotesObject:
for (MeetingNote *meetNote in value) {
[meetNote removeTasks:meetNote.tasks];
}
[self didChangeValueForKey:#"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
}
// Note: This code is not compiled and my contain errors.
This code will automatically enforce the constraints above without having to do anything else. You could also customize the Task subclass to set its name automatically based on some attributes of the MeetingNote object it is related to.
Now you have all the why logic in the data model and your constraints are automatically enforced. This may not be the exact solution you need but you get the idea.
Fun question. My experience here is limited, but I couldn’t resist attempting an answer:
If you expect a lot of edits to MeetingNote that need to be immediately reflected in Task, or vice versa, a relationship would automatically keep the loaded objects mutually updated. (I base this on Richard Stahl’s post here: fetched properties vs. relationships.) Otherwise the fetched property might be more efficient faulting-wise.
But why are you doing the relationship between MeetingNote and Tag as one-to-many? That means a tag can have only one MeetingNote. Right? So any time a meetingNote gets tagged “task,” a separate tag has to be created. Wouldn’t many-to-many be better?
And then, if going the relationship route, you would do a one-to-one relationship between MeetingNote and Task. Even if you expected multiple meetingNotes to share a task, since the task has to be derived directly from the meetingNote, you're going to be creating separate tasks anyway. And since you're creating one task instance per task-tagged meetingNote, those tasks shouldn't have multiple meetingNote relationships because that would create confusing duplication.
In short, I want to associate arbitrary key/value pairs with the objects of a Core Data entity, on an iPad app.
My current solution is to have a to-many relationship with another entity that represents a single pair. In my application, I have:
Entry <--->> ExtraAttribute
where ExtraAttribute has properties key and value, and the key is unique to the ExtraAttribute's Entry.
Although the code to deal with this is slightly complicated, it is acceptable. The real problem comes with sorting.
I need to sort those Entries that have a given ExtraAttribute by that attribute. Using the SQL store, it is apparently impossible for Core Data itself to sort the Entries by the value of the associated ExtraAttribute with a given key. (Frustrating, since this is possible with the other stores, and trivial in SQL itself.)
The only technique I can find is to sort the entries myself, then write a displayOrder attribute back to the store, and have Core Data sort by the displayOrder. I do that with the following class method on Entry. (This uses a some methods and global functions not shown, but hopefully you can get the gist. If not, ask and I will clarify.)
NSInteger entryComparator(id entry1, id entry2, void *key) {
NSString *v1 = [[entry1 valueForPropertyName:key] description];
NSString *v2 = [[entry2 valueForPropertyName:key] description];
return [v1 localizedCompare:v2];
}
#implementation Entry
...
// Unified builtin property and extraAttribute accessor;
// expects human-readable name (since that's all ExtraAttributes have).
- (id)valueForPropertyName:(NSString *)name {
if([[Entry humanReadablePropertyNames] containsObject:name]) {
return [self valueForKey:
[Entry propertyKeyForHumanReadableName:name]];
} else {
NSPredicate *p = [NSPredicate predicateWithFormat:
#"key = %#", name];
return [[[self.extraAttributes filteredSetUsingPredicate:p]
anyObject] value];
}
}
+ (void)sortByPropertyName:(NSString *)name
inManagedObjectContext:(NSManagedObjectContext *)moc {
BOOL ascending = [Entry propertyIsNaturallyAscending:name];
[Entry sortWithFunction:entryComparator
context:name ascending:ascending moc:moc];
[[NSUserDefaults standardUserDefaults]
setObject:name
forKey:#"entrySortPropertyName"];
}
// Private method.
+ (void)sortWithFunction:(NSInteger (*)(id, id, void *))sortFunction
context:(void *)context
ascending:(BOOL)ascending
moc:(NSManagedObjectContext *)moc {
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Entry" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *allEntries = [moc executeFetchRequest:request error:&error];
[request release];
if (allEntries == nil) {
showFatalErrorAlert(error);
}
NSArray *sortedEntries = [allEntries
sortedArrayUsingFunction:sortFunction context:context];
int i, di;
if(ascending) {
i = 0; di = 1;
} else {
i = [sortedEntries count]; di = -1;
}
for(Entry *e in sortedEntries) {
e.displayOrder = [NSNumber numberWithInteger:i];
i += di;
}
saveMOC(moc);
}
#end
This has two major problems:
It's slow, even with small data sets.
It can take an arbitrarily large amount of memory and hence crash with large data sets.
I'm open to any suggestions that are easier than ripping out Core Data and using SQL directly. Thanks so much.
EDIT Thank you for your answers. Hopefully this will clarify the question.
Here is a typical data set: There are n Entry objects, and each one has a distinct set of key/value pairs associated with it. Here I am listing the key/value pairs under each entry:
Entry 1:
Foo => Hello world
Bar => Lorem ipsum
Entry 2:
Bar => La dee da
Baz => Goodbye cruel world
Here I want to sort the entries by any of the keys "Foo", "Bar", or "Baz". If a given entry doesn't have a value for the key, it should sort like an empty string.
The SQLite store cannot sort by an unknown key using -valueForUndefinedKey:; attempting to do so results in an NSInvalidArgumentException, reason keypath Foo not found in entity <NSSQLEntity Entry id=2>.
As noted in the documentation, only a fixed set of selectors will work with sort descriptors using the SQL store.
EDIT 2
Suppose there are three instances E1, E2, and E3 of my entity, and the user attaches the custom properties 'Name' and 'Year' to each of these instances. Then we might have:
E1 Bob 2010
E2 Alice 2009
E3 Charles 2007
But we wish to present these instances to the user, sorted by any of these custom properties. For example, the user might sort by Name:
E2 Alice 2009
E1 Bob 2010
E3 Charles 2007
or by Date:
E3 Charles 2007
E2 Alice 2009
E1 Bob 2010
and so on.
First question is, why do you need to store the sort in the database? If you are alway sorting in the key property, just use a sort descriptor whenever you need to access them in a sorted order.
Second question, why are you writing your own sort routine?
This design seems rather complicated. I understand the need for arbitratary storage of key value pairs, I designed a similar system in my book. However I am unclear as to the need for sorting those values nor the need for a custom sort routine such as this one.
If you could explain the need behind the sorting I could probably suggest a better strategy.
Also, I would highly recommend looking into the two methods -valueForUndefinedKey: and -setValue: forUndefinedKey: as a cleaner solution to your issue. That would allow you to write code like:
[myObject valueForKey:#"anythingInTheWorld"];
[myObject setValue:someValue forKey:#"anythingInTheWorld"];
and follow proper Key-Value Coding rules.
Update
The -valueForUndefinedKey: design is only for use in code, it is not for use accessing the store. I am still a little unclear with your goals.
Given the following model:
Entity <-->> Property
In this design, Property has two attributes:
Key
Value
From here you can access any property on Entity via -valueForUndefinedKey: because under the covers, Entity will go out and fetch the associated Property for that key. Thus you get dynamic values on your Entity.
Now the question of sorting. With this design, you can sort directly on SQLite because you are really sorting on the Property entity. Although I am still unclear as to the final goal of the sorting. What value does it have? How will it be used?
Update: Design reconsidered
The last design I proposed was wrong. On deeper reflection, it is simpler than I proposed. Your goal can be accomplished with the original Entity <-->> Property design. However there is a bit more work to be done in the -setValue: forKey: method. The logic is as follows:
External code called -setValue: forKey: on an Entity.
The -setValue: forKey: method attempts to retrieve the Property.
If the Property exists then the value is updated.
If the Property does not exist then a Property is created for each Entity with a default value set (assumed to be an empty string).
The only performance hit is when a new key is introduced. Other than that it should work without any performance penalties.