Core Data NSFetchRequest Sort by Category Method Return Value - core-data

How do I sort my fetched results by a value that is returned by a method in a category of the entity I'm fetching?
In my category, I sum up several values from the entity's to-many relationship, then divide by the number of objects in the relationship, effectively creating an average that I return in my category method as a float value.
Here is my code:
In the Category.h
- (float)smallPenaltyAvg;
In the Category.m
- (float)smallPenaltyAvg{
float smallPenaltyAvg = 0;
for (Match *mtch in self.matches) {
smallPenaltyAvg += [mtch.penaltySmall floatValue];
}
if ([self.matches count] > 0) {
smallPenaltyAvg = (float)smallPenaltyAvg/(float)[self.matches count];
}
return smallPenaltyAvg;
}
And when I call it in the Core Data Table View Controller class that I created...
NSFetchRequest *poolRequest = [[NSFetchRequest alloc] initWithEntityName:#"Team"];
poolRequest.predicate = [NSPredicate predicateWithFormat:#"regionalIn.name = %#", _regionalToDisplay];
poolRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"smallPenaltyAvg" ascending:YES]];
And I have the Category.h file imported on every file previously mentioned outside of the Category.h file itself.
It gives me the error of:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath smallPenaltyAvg not found in entity <NSSQLEntity Team id=5>
Am I not allowed to do this?
If I am, what am I doing wrong?

I do not think this has anything to do with the kind of persistent store.
The trick is to create an appropriate attribute in the managed object model, and mark it as Transient. Then override the getter of this attribute to do your calculations.
Now your fetch request should work as expected (although there are some caveats with fetched results controllers).
As for the SQLite problem, when you add the SQLite store with
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType
configuration:(NSString *)configuration
URL:(NSURL *)storeURL
options:(NSDictionary *)options
error:(NSError **)error
just pass NSSQLiteStoreType as the storeType. The other options are binary and in-memory, so in this sense this is indeed the "default".

This is not possible when using a backing SQLite store.
My suggestion is you persist the average property, and maintain it yourself by overriding the Match setCategory: property and making the calculation there for every match added.

What I did to solve my problem was create a new attribute for every average or sum that I needed in the Team object from all of its Match objects' attributes and then created a method in the TeamCategory file that populated those averages and that method was called every time a Match object was inserted into the Team object. It took a while to do, but it works now. If there is a better solution, I'm still open for suggestions.

Related

Reading integer from Core Data

In my app I have a Core Data entity called Status with two attributes messageID as Integer32 and messageText as String.
I have a string stored in an SQL database which the app downloads on startup. The string from the database is broken down into two parts ID and text. An example message could be 011-Hello and the each part is stored in an array called messageParts. The first item in the array is the ID:
NSInteger newMessageID = [messageParts[0] integerValue];
I want to compare this ID with the one stored in Core Data such as:
if (messageID == newMessageID)
I get the newMessageID fine and I have a number to work with but I am totally confused as to how to handle the data type coming from Core Data. I can see that there is a number in the database using SQLlitebrowser and I have tried:
NSInteger *savedMessageID = [[self.status objectAtIndex:0] messageID];
and
NSInteger savedMessageID = [[self.status objectAtIndex:0] messageID];
But neither return the stored value. I think that this is a pointer issue but I am going around in circles here.
If you generate the NSManagedObject subclass from your xcdatamodeld (Xcode menu Editor > Create NSManagedObject Subclass…), you will find that the integer32 field is generated as an NSNumber...
This is maybe where you should take a look ?

iOS - Different MOCs and managed objects status

in the context of some tests I'm writing I've found the following issue. I make use of RestKit 0.20.2, among the other natural purposes, also to manage my coredata-related aspects.
In one of those tests, I create a CoreData entity A, containing a Date as one of its fields, by assigning it a Nil value (it's an optional field). My saving function performs the following code:
NSError* myError;
[[RKTestFactory managedObjectStore].mainQueueManagedObjectContext saveToPersistentStore:&myError];
NSLog(#"Save done on main queue with myError: %#", myError);
NSAssert1(myError == Nil, #"Coredata saving error: %#", myError);
After this save, an algorithm is run which operates on A and update that date field. This algorithm works in a private managed object context:
// Get a Local Managed Object Context
NSManagedObjectContext* myLocalMOC = [[DDRestKitManager sharedInstance].objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType trackChanges:YES];
(DDRestKitManager is a singleton managing about every RestKit-related aspect of my project). This algorithm first retrieves A with a FetchRequest within its private managed object context, then operates on it and finally updates its date field. It then saves every CoreData related aspect it dealt with (including the updated A-status) with a save on its private MOC.
When, in the previous test body, I need to invoke the very same algorithm again on the very same entity A after having updated some of its fields in order to test the new algorithm outcome, I need to put A's date field back to Nil before invoking the algorithm. This is what I do:
A.date_field = Nil;
[[TestCoreDataManager sharedInstance] save];
// <invoke_algorithm>
(TestCoreDataManager is a further singleton providing objects and saving them by means of the previously reported function). The problem is that when the algorithm retrieves again the object, the date_field is not Nil but still contains the previously assigned value.
It seems like the instance of A retrieved by the algorithm in its private context is not up-to-date wrt the underlying persistent store. Is there anyone who might tell me what I'm doing wrong?
Some more details
I've just moved the default semantics of this date field in order to be always not nil and to have 0 as default value. I rewrote the algorithm in order for the test condition to be [A.date_field timeIntervalSince1970] == 0 in place of A.date_field == Nil.
In my code, in order for that test to be met, I use the following code
A.date_field = [NSDate dateWithTimeIntervalSince1970:0];
[TestCoreDataManager save]; // [mainManagedObjectContext saveOnPersistentStore:&myError];
// Get a Local Managed Object Context
NSManagedObjectContext* myLocalMOC =
[[DDRestKitManager sharedInstance].objectManager.managedObjectStore
newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType
tracksChanges:YES];
<query_for_A_entities_in_the_store>;
At this point A.date_field contains the value it had before resetting to 0 seconds from 1970.
The even more strange part follows. If I replace
A.date_field = [NSDate dateWithTimeIntervalSince1970:0];
with
A.date_field = [NSDate dateWithTimeIntervalSince1970:1];
or
A.date_field = [NSDate dateWithTimeIntervalSince1970:-1];
and leave the rest of the code untouched, then the child managed object context fetch an A object with the date_field now up-to-date to the correct date and time (1970, 1:00:01 a.m. or 0:59:59 a.m. respectively).
This is driving me crazy.

Sorting NSFetchedResultsController using a to-many relationship property

SCENARIO
I have two entities: Item and ListDetail (which contains prices for different lists for every item). This is absolutely needed and I can't provide a price attribute for the Item entity because every item can have more prices for different dynamic lists (retail, b2b ecc.).
The relationship is:
Item (lists) <------->> (item) ListDetail
The current active list in my app change dinamically, so let's say I have an integer variable with the current active list: _ACTIVE_LIST_CODE_. When I need a price for an item object I use an helper method on the Item class:
-(NSNumber*) getPrice {
NSSet *lists=[self.lists filteredSetUsingPredicate: [NSPredicate predicateWithFormat:#"listId == %d",_ACTIVE_LIST_CODE_]];
ListDetail *activeList=[[lists allObjects] objectAtIndex:0];
return activeList.price;
}
THE PROBLEM
I use a UITableView with NSFetchedResultController in order to select and show some items for different sections. Nothing special. I would like to order the fetchedObjects using the items price for the active list. If price was an attribute of Item I would added simply a sort descriptor to the fetch request like so:
[NSSortDescriptor sortDescriptorWithKey:#"price" ascending:YES];
But as said before this is not possible, price is a dynamic attribute.
If using transient properties was possible for sort descriptors, I would set a price transient properties calculated on fly using my helper method. Nothing to do.
Using a keypath in the descriptor like "lists.price" is not possible (or maybe I don't know how to do that), just because it's a to-many relationship and it's modeled with a NSSet.
I tried some workaround, without success:
1) observing _ACTIVE_LIST_CODE_ changes to set items price in a non-transient attribute.
2) after the fetch request, before presenting the table view, reorder a brand new array with fetched objects using the transient "price" property, iterate the orderdered array following an ascending integer index "i" and assigning this value to a non-transient property "order" for the Item entity. Using "order" for sort descriptor in the fetch request. (This approach is described here: Re-ordering NSFetchedResultsController)
Both of them works, but they slow down performance because I have thousands of items in the fetch results... Any idea?
How about fetching ListDetail instead? You could restrict and sort with the appropriate predicates and sort descriptors, exactly as you propose.
fetchRequest.predicate =
[NSPredicate predicateWithFormat:#"listID = %#", activeListCode];
fetchRequest.sortDescriptors =
#[[NSSortDescriptor sortDescriptorWithKey:#"price" ascending:YES]];
Now, to group by some attribute of item should be simple and efficient because it is a to-one relationship. Your fetched results controller's sectionNameKeyPath can be something like
#"item.category"

What is the correct way for quering a NSNumber in a NSArray within CoreData?

I have a Entity with a column of type ID named "responsibleUsers". In this column I store an Array containing NSNumbers.
I want to fetch all objects of this entity, that match my current User. Therefore i create following predicate:
[NSPredicate predicateWithFormat: #"%# IN responsibleUsers",[NSNumber numberWithInteger: curUser.oID.integerValue] ]
whatever I try, my App crashes. Once with a EXC_BAD_ACESS, once with "unimplemented SQL generation for predicate nsnumber"
What is the correct way to query my entity?
The query you are trying assumes that you have two entities: the entity you querying (let's call it Group) and another one, perhaps called User, which is set up as a to-many relationship from Group called responsibleUsers. You would then be able to use the predicate you suggest:
[NSPredicate predicateWithFormat:#"%# IN responsibleUsers, aUser];
This would be the recommended use of Core Data object graphs.
In your setup, it seems you have an array of NSNumber set as a property rather than a relationship. Therefore you cannot use such a query. You simply have to retrieve the array and query the array.
BOOL containsResponsibleUser = NO;
for (NSNumber *n in aGroup.responsibleUsers) {
if ([n isEqualTo:[NSNumber numberWithInteger: curUser.oID.integerValue]])
containsResponsibleUser = YES;
}
If you are indeed querying something like a group of users, I would recommend the first approach. If you are querying some kind of user, I would suggest a BOOL property responsible as the most efficient solution.

Core Data uniqueness

Is there any way I can validate a value updated in a Core Data entity's property against values of the property in other entities in the collection?
At the moment I create an entity with some default values, add it to arrangedObjects, then get the user to modify the various property values. However, I would like to check a particular property and make sure there're no other entities in the array with the same value for that property. What's the best way to do this?
Many thanks,
Dany.
Manually checking is only a few lines of code with a fast enumeration loop:
BOOL unique = YES;
for (NSManagedObject *obj in collection) {
if (obj.property == value) {
unique = NO;
break;
}
}

Resources