Difference in data when using core data relationships - iphone

I'm using core data to fetch values from an sql lite database for my iphone app. I'm pretty new to the syntax so I might be missing a few key infos.
My fetch request looks like this:
NSEntityDescription *difficultyDescription = [NSEntityDescription entityForName:#"Difficulty" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:difficultyDescription];
NSArray *diffResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
After that, I'm looping through the results by using this snippet:
systems = [[NSMutableArray alloc] init];
values = [[NSMutableArray alloc] init];
for (NSManagedObject *diff in diffResults) {
[systems addObject:diff];
[values addObject:[diff valueForKey:#"difficultyValues"]];
}
What I don't get about this is, that when I log systems by using
NSLog(#"%#", [[systems objectAtIndex:0] valueForKey:#"name"]);
I get the result in plain text. For example "some value".
When logging the results in the "values" array however I get this result:
{(
"some value"
)}
I'm using the same logging call:
NSLog(#"%#", [[values objectAtIndex:0] valueForKey:#"value"]);
The entities difficulty and difficultyValues have a one to many relationship.
How can I display the value in the array "values" like the one in "systems"? I need it later on for a label.

This seems to be a fundamental misunderstanding. difficultyValues is a relationship, while name is an NSString attribute. That's why it appears differently in NSLog(), which works by sending objects a -description message.
The value for difficultyValues will be an NSSet which contains multiple managed objects for the entity difficulty.
Here's what you should do:
NSLog(#"%#", [[systems objectAtIndex:0] valueForKey:#"name"]); // NSString*
// let's loop through all the difficulties of this item
for (NSManagedObject* aDifficulty in [[[systems objectAtIndex:0] valueForKey:#"difficultyValues"] allObjects]) {
NSLog(#"%#", [aDifficulty valueForKey:#"name"]);
}
This, of course, assuming your difficulty entity has a name attribute.

Related

Fetching strings (or other variables) from a CoreData fetch request?

I have Core Data setup in my app and need to fetch a bunch of items and then access the properties I choose of those fetched items. I am able to successfully fetch a bunch of results like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
This gives me an array with my results, great. Now from this how can I for example get the 'name' property from these results? In this specific circumstance I want to load an array with all of the fetched results 'name' strings.
If I read your question correctly, you are able to fetch your NSManagedObjects without difficulty, but would like to derive another NSArray with name properties on those managed objects?
Then you can use the valueForKeyPath on the NSArray (extending your original code):
NSArray *names = [result valueForKeyPath:#"name"];
You can use the key-value:
for (NSManagedObject *fetchedResult in result) {
NSLog(#"name = %#", [fetchedResult valueForKey:#"name"]);
}
or if you created your custom NSManagedObject:
for (EntityObject *fetchedResult in result) {
NSLog(#"name = %#", [fetchedResult name]);
}

CoreData Math Functions

I've got a column with integer values in CoreData. While retrieving results from it, I want the column values to be subtracted with a number.
Something like: columnValue - someNumber (this number is entered by user)
I know I may have to use NSPredicate for this, but am unaware if there's a function or syntax for it.
The alternate right now I have is to iterate all column values and subtract with 'someNumber'. But I think there should be a better and efficient way to do this.
Edit: Code from #salo.dm 's answer
- (NSDictionary *)myFetchResults {
//Predicate works fine
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:pred1, pred2, nil]];
/*Sort Descroptor - Sorting by 4 columns*/
NSSortDescriptor *sortDesc1 = [NSSortDescriptor sortDescriptorWithKey:#"Column1" ascending:YES];
NSSortDescriptor *sortDesc2 = [NSSortDescriptor sortDescriptorWithKey:#"Column2" ascending:YES];
NSSortDescriptor *sortDesc3 = [NSSortDescriptor sortDescriptorWithKey:#"Column3" ascending:YES];
NSSortDescriptor *sortDesc4 = [NSSortDescriptor sortDescriptorWithKey:#"Column4" ascending:YES];
/*Get Data*/
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableName" inManagedObjectContext:context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDesc1, sortDesc2, sortDesc3, sortDesc4, nil]];
NSArray *listData = [context executeFetchRequest:fetchRequest error:nil];
/*Create subtract expression*/
NSExpressionDescription *subExp1 = [[NSExpressionDescription alloc] init];
[subExpLatitude setName:#"subtraction1"];
[subExpLatitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column3"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber1]],
nil]]];
[subExp1 setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *subExp2 = [[NSExpressionDescription alloc] init];
[subExpLongitude setName:#"subtraction2"];
[subExpLongitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column4"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber2]],
nil]]];
[subExp2 setExpressionResultType:NSDoubleAttributeType];
/*Get difference data*/
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, subExp2, nil]];
NSArray *listDifference = [context executeFetchRequest:fetchRequest error:nil];
NSLog(#"Subtraction 1: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction1"]);
NSLog(#"Subtraction 2: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction2"]);
NSMutableDictionary *dictResult;
[dictResult setObject:listData forKey:#"Data"]
[dictResult setObject:listDifference forKey:#"Difference"]
return dictResult;
}
Edit: Get coredata object
This doesn't work.
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]]; //Searches for a column for the name specified
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Had to change it to below to get it working (Assuming this is the correct way)
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForEvaluatedObject]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];
I added expEntity to the setPropertiesToFetch list. Now I get two values in the dictionary.
{
TableNameEntity = "0x5e22120 <x-coredata://1A659A52-9321-4ACD-992B-04F20E7BDCED/TableNameEntity/p1640>";
subtractionValue = "-24.13";
}
When I try to retrieve and access TableNameEntity from the dictionary, the app crashes.
TableNameEntity *tableEntity = (TableNameEntity *)[dict objectForKey:#"TableNameEntity"];
tableEntity.column1 //This is not the exact code. But this operation crashes with error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSObjectID_48_0 column1]: unrecognized selector sent to instance 0x5e22120'
Here if you notice, the value for key TableNameEntity is contained in quotes, so I guess its being returned as a string.
See if you can correct what I've done wrong.
I've tried an alternate to get columns values in the dictionary. Here it is (this works fine). But I guess its not a good approach.
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, #"column1", #"column2", ... #"columnN" nil]];
You could make the calculation in a fetch request as follows:
- (NSArray *)myFetchResults
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"myEntity" inManagedObjectContext:myContext];
request.resultType = NSDictionaryResultType;
NSExpressionDescription *subExDescr = [[NSExpressionDescription alloc] init];
[subExDescr setName:#"subtraction"];
[subExDescr setExpression:[NSExpression expressionForFunction:#"subtract:from:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForConstantValue:[NSNumber numberWithInt:someNumber]],
[NSExpression expressionForKeyPath:#"myAttribute"],
nil]]];
[subExDescr setExpressionResultType:NSInteger64AttributeType];
request.propertiesToFetch = [NSArray arrayWithObject:subExDescr, nil];
NSError *err = nil;
NSArray *results = [self.moContext executeFetchRequest:request error:&err];
[request release];
[err release];
return results;
}
The fetch results will be an array of dictionaries. You can access the result for the nth value in the column as follows:
NSArray *results = [self myFetchResults];
NSDictionary *nthDict = [results objectAtIndex:n];
NSInteger nthValue = [nthDict objectForKey:#"subtraction"];
Note that this code is untested. As is, I believe it will operate on all items in the column. If you want to operate only on selected items, you can add a predicate to select the items you want to operate on.
You could also look up the documentation for NSExpression and build all sorts of different operations. The class is a bit dense, but the Apple documentation has some snippets of code that help to understand how to use it. And I hope the above example illustrates how to incorporate it into a fetch request.
EDIT: CORRECTION
The entity, of course, has to be specified in the fetch request. I had initially left that out, but have now corrected the code.
EDIT: RESPONSE TO COMMENT
I'm not sure I understand what you're asking, but this may be it. You can create expression descriptions as follows:
NSExpressionDescription *expLatitude = [[NSExpressionDescription alloc] init];
[expLatitude setName:#"latitude"];
[expLatitude setExpression:[NSExpression expressionForKeyPath:#"Column3"]];
[expLatitude setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Then, you add them to the propertiesToFetch array, as two more objects. Each dictionary in the fetch results will now have the latitude, the subtraction resulting from that same latitude, and the corresponding objectID of the entity that contained that latitude. The dictionaries will be ordered in the results array according to your sort descriptors. (I haven't tried the objectID expression, but I think it should work fine.)
Basically, your results are ordered in the exact same order as a traditional fetch request with the same predicate and the same sort descriptors, that is, for a fetch request with the default result type NSManagedObjectResultType.
I hope this answers your question. If not, don't hesitate to ask again. But, I may take a while to answer because it's sleep time for me now.
EDIT: RESPONSE TO 'GET COREDATA OBJECT'
Good catch on finding the correct expression to get the object ID! (Seeing it, the expression I offered for this now looks obviously wrong.)
As to the exception you're getting, it makes sense. The value returned in the fetch results is not the managed object itself, it's only the managed object's ID. To access the managed object, I think the following should work:
NSManagedObjectID *myObjectID = [dict objectForKey:#"TableNameEntity"];
TableNameEntity *tableEntity = (TableNameEntity *)[context objectWithID:myObjectID];
tableEntity.column1
The context above is the NSManagedObjectContext.
However, I think I prefer your final solution. I didn't know you could combine NSExpressionDescriptions with properties in the propertiesToFetch array. Good to know!
More importantly, it may be faster to get all the properties you need in the fetch than to get only the objectID from the fetch and get the properties later. Getting the objectID generally does not fire the fault for the entity. I believe the fault will be fired later, when you access the properties. It will fire once, when accessing the first property, or multiple times, once for each property. (I'm not sure which.) [For an explanation of faulting, see Firing Faults.]
My recommendation is that including all the properties you need in propertiesToFetch is the best approach. (You may try getting the objectID, if you prefer. But, if you find it's slow, you can go back to getting all the properties in the fetch.)
Fetch requests and expressions are poorly documented. You have to play with them a bit to get the syntax right. You seem to be doing very well.

CoreData Edit one attribute in One to Many relationship

I am new to CoreData and been looking to all the books and examples but none of them really tell me how to do this, so any help is greatly appreciated.
Basically, I have 2 Entities in one to Many relation. [other relationships are not important in this case]
The relationship and entities:
Now I can get All the MedicalCondition Entity based on given Profile Entity using NSFetchRequest
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"MedicalCondition" inManagedObjectContext:delegate.managedObjectContext];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"condition" ascending:YES]];
request.predicate = [NSPredicate predicateWithFormat:#"MedicalToProfile = %#", myProfile];
//request.fetchBatchSize = 20;
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:delegate.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
BOOL success = [frc performFetch:&error];
NSArray *fetchedObjectsFromCore;
[request release];
if (success) {
fetchedObjectsFromCore = [frc fetchedObjects];
}
This is ok. Now the problem comes in when I want to update one particular entry. I am not sure how to do it. I can add more MedicalCondition object just fine. But when it comes to edit, I am not sure.
The only way I cant think of is to have "ID" attribute in entity. I think there must be a better solution than this. Please help ! Thankz so much.
If you have a Core Data object from a valid context, then editing it is very easy. Modify the object data, then save its context. Let's say you have a MedicalCondition object that you got hands on somehow.
MedicalCondition *condition;
// modify a field
condition.date = [NSDate date];
// save
NSError *error;
[managedObjectContext save:&error];
Also if you have a given Profile object, you can get all associated MedicalCondition objects directly without having to perform a fetch as long as you do not care about order.
Profile *someonesProfile = ...;
someonesProfile.conditions
// and access a profile from a given MedicalCondition since
// it seems to be a bi-directional relationship.
MedicalCondition *someCondition = ...;
someCondition.profile.dateofbirth;
You should give more meaningful names to the relationships instead of MedicalToProfile, ProfileToMedication, etc. For example, instead of ProfileToMedical, maybe use:
medicalConditions
which is semantically nicer, and reads better in code:
someonesProfile.medicalConditions
Thankz again Anurag.
I kindna got it working.
this is how I did it
NSSet *newMedical = myProfile.ProfileToMedical;
NSMutableArray *arrayMedical = [NSMutableArray arrayWithArray:[newMedical allObjects]];
MedicalCondition *c = [arrayMedical objectAtIndex:1];
c.condition = #"Amazing";
And It update the right place :)
But now when I call again
NSSet *details = myProfile.ProfileToMedical;
NSMutableArray *arrayDetail = [NSMutableArray arrayWithArray:[details allObjects]];
The return NSSet is show the updated condition to be at other index. I understand that is because myProfile.ProfileToMedical is unsorted? so I must always sort the array first before I view/edit attribute to ensure the consistency?
Thankz again

Core Data performance issues

I have an iPhone app that has the needs to avoid inserting duplicate records, at first I thought I could just test each object in my in-memory array, create a fetch request for each object and test if it exists. But this is proving slow on the device, very slow (about 5 seconds, which is not acceptable).
I have been trying to piece together how to create some smart predicate that I could use in order to get this to work efficiently but without much success.
My objects have a NSNumber field that I have set as the "Identity Property" and also non-optional. This field is called sampleTime (again, this is NOT a date, but a NSNumber)
Here is my idea (borrowed from other threads and even some of my own questions of SO):
Obviously doing a fetch per object (around 380 objects) is not going to work for performance, so I was under the impression that I could do most of it in memory and it would be faster. I need to create some predicate that uses the IN clause, then iterate over that fetch result, testing if any one of the objects is inside that result set, if NOT then insert it, if SO then do nothing.
But my implementation is not working:
NSMutableArray *timeStampArray = [[NSMutableArray alloc] init];
for (id emTmp in myListOfObjects)
[timeStampArray addObject: [NSNumber numberWithInt:[[emTmp sampleTime] timeIntervalSince1970]]];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:[NSEntityDescription entityForName:#"ElectricalMeasurementEntity" inManagedObjectContext:context]];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"sampleTime in %#", timeStampArray]];
NSArray *results = [context executeFetchRequest:fetch error:nil];
int resCount = [results count];
But resCount is always zero, and I dont know why...
BTW, myListOfObjects contains business objects which also have a sampleTime property which IS an NSDate type.
EDIT
Ok, update, I got the basics working. The reason why I was not getting any results was because the loop that created the array was using the id type, which when used the way I was using it was not creating NSNumber objects correctly.
Now I do this:
for (id emTmp in myListOfObjects)
{
Measurement *t = (Measurement*)emTmp;
NSTimeInterval d = [t.sampleTime timeIntervalSince1970];
[timeStampArray addObject: [NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]];
}
which creates a nice list which works very well.
However, I then go on to do this:
NSMutableArray *itemsToInsert = [[NSMutableArray alloc] init];
for (id em in myListOfObjects)
{
BOOL found = NO;
Measurement *t = (Measurement*)em;
for (id e in results) //results from Fetch Request which is now populated properly
{
MeasurementEntity *entity = (MeasurementEntity*)e;
if ([entity.sampleTime isEqualToNumber:[NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]])
{
found = YES;
break;
}
}
if (!found)
[itemsToInsert addObject: t];
}
This loop (for around 850 objects, on the iPhone 3Gs) takes around 10 - 12 seconds, which I can see why (when 850*850 = 722500 loops!). Can I be more efficient about this?
Thanks
You need to strip the fetch down such that it will only check the one property. Then do all your comparisons with predicates for speed. Something like this:
NSArray *newData=[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:6],nil];
NSManagedObject *mo;
for (int i=0; i<5; i++) {
mo=[NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.moc];
[mo setValue:[NSNumber numberWithInt:i] forKey:#"numAttrib" ];
}
[self saveContext];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
NSEntityDescription *testEntity=[NSEntityDescription entityForName:#"Test" inManagedObjectContext:self.moc];
[fetch setEntity:testEntity];
// fetch only the one property you need to test
NSDictionary *propDict=[testEntity propertiesByName];
[fetch setPropertiesToFetch:[NSArray arrayWithObject:[propDict valueForKey:#"numAttrib"]]];
// Return as dictionaries so you don't have the overhead of live objects
[fetch setResultType:NSDictionaryResultType];
// fetch only those existing property values that match the new data
NSPredicate *p=[NSPredicate predicateWithFormat:#"numAttrib in %#",newData];
[fetch setPredicate:p];
NSArray *fetchReturn=[self performFetch:fetch];//<-- my custom boilerplate
// extract the existing values from the dictionaries into an array
NSArray *values=[fetchReturn valueForKey:#"numAttrib"];
// filter out all new data values that already exist in Core Data
p=[NSPredicate predicateWithFormat:#"NOT (SELF in %#)",values];
NSArray *unmatchedValues=[newData filteredArrayUsingPredicate:p];
NSLog(#"unmatcheValues=%#",unmatchedValues);
... which outputs:
unmatcheValues=(
6
)
Now you only need to create new managed objects for the values returned. All other new values already exist.
This may seem an obvious suggestion, but if [context executeFetchRequest:fetch error:nil] is not returning any results, seems like the first thing to do is check for errors instead of ignoring them (by setting error:nil).
Something like:
NSError *error = nil;
NSArray *results = [context executeFetchRequest:fetch error:&error];
if (results == nil) { // fetch failed - huh?
NSLog(#"Fetch error %#, %#", error, [error userInfo]);
}

Loading all the values of an attribute of core data to an array

I have an attribute "term" which is a NSString in my core data "Event".
When the table view is loaded I want all the values of "name" to be loaded to an array.
I used the code
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Event *event = nil;
event = [fetchedResultsController objectAtIndexPath:indexPath];
if(searching){
cell.textLabel.text = [copyListOfItems objectAtIndex:indexPath.row];
}else{
if(event.term){
[listOfItems addObject:event.term];
}
cell.textLabel.text = event.term;
cell.detailTextLabel.text = event.definition;
}
}
The problem is that all the terms are not loaded to the NSMutableArray listOfItems. It is loaded when the table cells is scrolled to bottom.
How to load all the contents of "term" from core data to the array listOfItems.
Any help will be greatly appreciated.
You are most likely looking at Core Data wrong. Core Data is not a database. It is an object graph that happens to persist to a database. Having said that, your question is better asked as "how can I load all instances of the entity 'Event' and access its term property".
To do that, you want to want to build and execute a NSFetchRequest against the 'Event' entity with no predicate.
NSManagedObjectContext *moc = ...;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:moc]];
NSError *error = nil;
NSArray *events = [moc executeFetchRequest:request error:&error];
NSAssert2(events != nil && error == nil, #"Error fetching events: %#\n%#", [error localizedDescription], [error userInfo]);
From here you can then use 'KVC' to retrieve an array of the name property:
NSArray *namesArray = [events valueForKey:#"name"];
Which will result in an array with just that string populated.
However the better question is, why do you want the array if you are displaying everything in a table? The NSFetchedResultsController already has all of the 'Event' entities retrieved for you; it is only the display that is bound to the table. What is your goal?
Update
Thank You very much for the detailed answer Mr Marcus... My goal is to search a particular "term" from the core data using search bar. Is there any better method for this to do?
Yes there is, and there are many solutions to the problem. You can access all of the results from the existing NSFetchedResultsController using its -fetchedObjects property. From there you can get a filtered array by applying a NSPredicate against that array using -filteredArrayUsingPredicate: and you can then use that array in your search results.
If you do a search on SO for Core Data and the UISearchDisplayController, I suspect you will turn up many results and suggestions.