Adding NSManagedObjects to a one-to-many relationship - iphone

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

Related

How to get an object at an index of a to-many NSSet in NSMangedObject category

Here is my sitch. Let's say that I have Company and Employee object types, with company having to-many Employee's. I create a category for Company and add a method
-(Employee *)getNextEmployee {
return (Employee *)self.employees[self.currentEmployeeIndex++];
}
I realize that I can't access an NSSet by an index, but since it's a category I can't add an NSArray instance variable either. This method is being called frequently, so creating an NSArray for each call would be highly inefficient.
Again, please ignore specifics here, it's just an example.
Edit: Scratch my previous answer suggesting the use of the allObjects property of NSSet. Thinking about it made me realize allObjects is not guaranteed to return the array on the same order every time.
Instead I suggest you use a linked list. Create a previousEmployee property for your Employee class and every time you create a new Employee, using some logic you assign the previous employee. It could be based on the date of entry:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Employee"];
request.fetchLimit = 1;
NSSortedDescriptor *sortDescriptor = [sortDescriptorWithKey:#"entryDate" ascending:NO];
// Do further setup...
Employee *latestEmployee = // fetch result...
Employee *newEmployee = // create new employee
newEmployee.previousEmployee = latestEmployee;
There's no getting around having an array if you want ordering (implied by next). The way to have an array economically is to make a NSManagedObject subclass and add an array property to it. Update it when you do adds/removes from the set.
Both #danh and #pedro.m gave great answers. I ended doing kind of a mixture of the two. I created a new class to manage my process instead of a subclass, and I pulled the employees straight from the relationship attribute of the company. Something like this:
#implementation CompanyProcessRunner
-(void)setCompany:(Company *)newCompany
{
company = newCompany;
employees = [company.employees sortedArrayUsingDescriptors:[NSSortDescriptor descriptorWithKey:#"order" ascending:YES]];
}
-(Employee *)nextEmployee
{
company.currentEmployeeIndex += 1;
return (Employee *)[employees objectAtIndex:company.currentEmployeeIndex];
}
#end
Thanks for the help everyone!

How i can access Sub-Entity attributes from Suber-Entity in Core data

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).

Arbitrary Attributes in Core Data

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.

Good practices for multiple language data in Core Data

i need a multilingual coredata db in my iphone app. I could create different database for each language but i hope that in iphone sdk exist an automatically way to manage data in different language core data like for resources and string.
Someone have some hints?
I've done something similar to Shortseller, but without the use of categories.
InternationalBook and LocalizedBook are both custom managed objects with a one-to-many relationship (one international book to many localised books).
In the implementation of InternationalBook, I've added a custom accessor for title:
- (NSString *)title {
[self willAccessValueForKey:#"title"];
NSString *locTitle = nil;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"locale==%#", [DataManager localeString]];
NSSet *localizedSet = [self.localizedBook filteredSetUsingPredicate:predicate];
if ([localizedSet count] > 0) {
locTitle = [[localizedSet valueForKey:#"localizedTitle"] anyObject];
}
[self didAccessValueForKey:#"title"];
return locTitle;
}
[DataManager localeString] is a class method which returns the user's language and country code: en_US, fr_FR, etc. See documentation on NSLocale for details.
See the "Custom Attribute and To-One Relationship Accessor Methods" section of the Core Data Programming Guide for an explanation of willAccessValueForKey: and didAccessValueForKey:.
When populating the data, I grab a string representing the user's current locale ([DataManager localeString]), and store that along the with localised book title in a new LocalizedBook object. Each LocalizedBook instance is added to an NSMutableSet, which represents the one-to-many relationship.
NSMutableSet *bookLocalizations = [internationalBook mutableSetValueForKey:#"localizedBook"]; // internationalBook is an instance of InternationalBook
// set the values for locale and localizedTitle
LocalizedBook *localizedBook = (LocalizedBook *)[NSEntityDescription insertnNewObjectEntityForName:#"LocalizedBook" inManagedObjectContext:self.bookMOC];
localizedBook.locale = [DataManager localeString];
localizedBook.localizedTitle = theLocalizedTitle; // assume theLocalizedTitle has been defined.
[bookLocalizations addObject:localizedBook];
[bookLocalizations setValue:localizedBook forKey:#"localizedBook"];
Since the localised titles are being stored in the LocalizedBook managed object, you can make the title attribute a transient, but if you do that you can't use title in a predicate.
The nice thing about this approach is that the implementation of the to-many relationship is transparent to any consumers. You simply request internationalBook.title and the custom accessor returns the appropriate value based on the user's locale behind the scenes.
I have generated model classes for the core data entities.
Then I defined category helper classes with functions to set and get the multilanguage properties (e.g. name).
So I have a Product (with e.g. code and price) and a ProductLanguage (with language and name properties) Entity.
I never directly access ProductLanguage, but always use the name function defined on the the Product model (via category). This has worked well for me sofar.
Like Gordon, I use quite similar code, but not written files generated by model. I use this code in my .m file where I want to show the data.
As I start from Apple template, I put this code exactly in
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
of my TableViewController.m
P.S: Just to understand, I use these prefixes: tbl_ for tables (entities), rel_ for relationships, fld_ for fields (attributes).
Hope this helps.
NSSet *sourceSet = [NSSet setWithArray:[[tbl_MainTable rel_Localization]allObjects]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"fld_Locale == %#", [[NSLocale preferredLanguages] objectAtIndex:0]];
NSSet *filteredSet = [sourceSet filteredSetUsingPredicate:predicate];
//NSLog(#"%#", filteredSet); NSLog(#"%#", [[filteredSet valueForKey:#"fld_Name"] anyObject]);
if ([filteredSet count] > 0)
{
[cell.detailTextLabel setText:[[filteredSet valueForKey:#"fld_Name"] anyObject]];
}

Copying whole object graph - copy a relation

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