Core Data Predicates with Subclassed NSManagedObjects - iphone

I have an AUDIO class. This audio has a SOUND_A subclass and a SOUND_B subclass. This is all done correctly and is working fine.
I have another model, lets call it PLAYLIST_JOIN, and this can contain (in the real world) SOUND_A's and SOUND_B's, so we give it a relationship of AUDIO and PLAYLIST.
This all works in the app.
The problem I am having now is querying the PLAYLIST_JOIN table with an NSPredicate. What I want to do is find an exact PLAYLIST_JOIN item by giving it 2 keys in the predicate
sound_a._sound_a_id = %# && playlist.playlist_id = %#
and
sound_b.sound_b_id = %# && playlist.playlist_id = %#
The main problem is that because the table does not store sound_a and sound_b, but stored audio, I cannot use this syntax. I do not have the option of reorganizing the sound_a and sound_b to use the same _id attribute name, so how do I do this?
Can I pass a method to the predicate? something like this:
[audio getID] = %# && playlist_id = %#

It gets a little complicated but you need to add a third condition to the predicate:
(entity.name = sound_a && _sound_a_id = %# && playlist.playlist_id = %#) && (entity.name = sound_b && sound_b_id = %# && playlist.playlist_id = %#)
This is assuming you are querying against the audio abstract and telling it to return subclasses. Because the condition is checked left to right, if the first condition fails it will move on and not throw errors because _sound_a_id does not exist.
The first condition is referencing the NSEntityDescription that is a part of the NSManagedObject and its name attribute is just a string.

Related

Correct way to modify results array from NSFectchRequest without changing Core Data objects

I am retrieving a fetch from my core data database and trying to iterate through the data and make changes to the data, if necessary. When I change the data in the results array, it turns out that my database is changing in the back end as well, without doing a save. I am wondering what would be a good practice to use to change the data without affecting the back end data.
Here is the code I change the data with:
self.singleDayDataPointsForGraph = [[self fuelPurchaseDataForTimePeriodInMonths:self.numberOfMonthsForGraphView] mutableCopy];
for (int i = 0; i < self.singleDayDataPointsForGraph.count; i++) {
FuelPurchase *currentFuelPurchase = [self.singleDayDataPointsForGraph objectAtIndex:i];
if (i < self.singleDayDataPointsForGraph.count + 1 && self.singleDayDataPointsForGraph.count >= 2) {
FuelPurchase *purchaseToCompare = [self.singleDayDataPointsForGraph objectAtIndex:i + 1];
NSDate *firstDate = currentFuelPurchase.dateTimeStamp;
NSDate *secondDate = purchaseToCompare.dateTimeStamp;
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"yyyy-MM-dd"];
if([[dateComparisonFormatter stringFromDate:firstDate] isEqualToString:[dateComparisonFormatter stringFromDate:secondDate]] ) {
float firstValue = [purchaseToCompare.fillSavings floatValue];
float secondValue = [currentFuelPurchase.fillSavings floatValue];
purchaseToCompare.fillSavings = [NSNumber numberWithFloat:(firstValue + secondValue)];
[self.singleDayDataPointsForGraph removeObjectAtIndex:i];
}
}
The fuelPurchaseDataForTimePeriodInMonths: method is what performs the fetch and returns an NSArray of results. self. singleDayDataPointsForGraph is an NSMutableArray that stores the results array as a mutable copy. This method basically checks two entries in the database to see if they are the on the same day and if they are, then it adds the fuel purchase amounts to each other and deletes one of the records. I don't want this to change my back end data, but it is.
Thanks very much.
It is doing the right thing. If you take a core data object and modify it, it will reflect immediately whether you save it ot not. The saving part ensures, that if you quit the application and come back the data is saved as well.
So for your situation, I would avoid modifying the actual core data object. Rather create a structure which imitates the core data object and modify that structure.
Example, say my coredata object is Person with attributes name and age.
The object a get from a fetch is say
person1.
You have a class PersonSub with same attributes.
Now you can create
PersonSub *personSub = [[PersonSub alloc] init]; //You can create a custom init to initilize from Person core data if you like.
personSub.name = person1.name;
personSub.age = person1.age;
Now you can modify as follows
personSub.age = personSub.age + 1;

Core data only storing last object of JSON feed

I´m using Core Data as local storage in my app. I´ve set it up properly and made subclasses of NSManagedObject for each entity. However, when I´m trying to insert values into my store, it only inserts the last object from my JSON feed.
res = [JSONHandler requestJSONResponse:jsonString];
shows = [res valueForKeyPath:#"Show.Name"];
NSUInteger showIndex = 0;
for(NSString *showName in shows){
showObject = [NSEntityDescription insertNewObjectForEntityForName:#"Show" inManagedObjectContext:managedObjectContext_];
showObject.name = showName;
showObject.iD = [[res valueForKeyPath:#"Show.Id"]objectAtIndex:showIndex];
showObject.desc = [[res valueForKeyPath:#"Show.Description"]objectAtIndex:showIndex];
showObject.activityType = [[res valueForKeyPath:#"Show.ActivityType"]objectAtIndex:showIndex];
showIndex++;
}
This only stores the last object from my JSON feed. Any idea why?
EDIT: It works fine when I do this:
res = [JSONHandler requestJSONResponse:jsonString];
shows = [res valueForKeyPath:#"Show.Name"];
NSUInteger index = 0;
for(NSString *showName in shows){
show = [NSEntityDescription insertNewObjectForEntityForName:#"Show" inManagedObjectContext:managedObjectContext_];
[show setValue:showName forKey:#"name"];
[show setValue:[[res valueForKeyPath:#"Show.Id"]objectAtIndex:index] forKey:#"iD"];
[show setValue:[[res valueForKeyPath:#"Show.Description"]objectAtIndex:index] forKey:#"desc"];
[show setValue:[[res valueForKeyPath:#"Show.ActivityType"]objectAtIndex:index] forKey:#"activityType"];
index++;
}
It´s basically the same thing, isn´t it? But I want to use subclasses of NSManagedObject instead of doing like I did above. Because in the snippet above show is NSManagedObject *show instead of what it should be: Show *show.
How many shows are there? You can find this by doing: NSLog(#"Number of shows: %d.", shows.count);, assuming that shows is an NSArray. It could be that your Core Data code is fine and the JSON parsing itself is at fault.
EDIT: Also, are you correctly saving the changes to the persistent store?
Usually when you see just one of several objects being saved like this, the problem is that a relationship that should be to-many is improperly set as to-one. No matter how many objects you try to add to the relationship, only the last one is set because the relationship can hold only one value.
I think in this circumstance the problem is most likely in the code of the custom subclass instead of the data model itself given that the data model works with generic NSManagedObjects.

Cocoa Core Data - Efficient Related Entities Counts

I am working on my first iPhone application and I've hit a wall. I'm trying to develop a 'statistics' page for a three entity relationship. My entities are the following:
Department - Name, Address, Building, etc.
People - Name, Gender (BOOL), Phone, etc
If I have fetched a specific department how do I filter those results and only return people that are Male (Gender == 0)?
If I do
NSLog(#"%d", [department.people count]);
I get the correct number of people in that department so I know I'm in the neighborhood. I know I could re-fetch and modify the predicate each time but with 20+ stats in my app that seems inefficient. Thanks for any advice!
You don't need to refetch:
NSPredicate* pred = [NSPredicate predicateWithFormat:#"gender == NO"];
NSUInteger count = [[department.people filteredArrayUsingPredicate:pred] count];
NSLog(#"%lu", (unsigned long)count);
Somehow gender==NO still looks strange though ;)
If copying is too expensive, you could use enumerators instead. E.g.:
NSUInteger CountIf(NSEnumerator* en, NSPredicate* pred) {
NSUInteger count = 0;
id obj;
while (obj = [en nextObject]) {
if([pred evaluateWithObject:obj])
++count;
}
return count;
}
NSUInteger count = CountIf([department.people objectEnumerator], predicate));
... though this would be ideally moved to a suitable category as say countOfObjectsMatchingPredicate:.
You could create NSPredicates representing your different filters and use NSSet's filteredSetWithPredicate: method. The count method will give you the number of entities matching the predicate. This isn't terribly efficient because you're creating a new set for each calculation, but it may be significantly faster than fetching each time.

Core Data: Save failing on mandatory field, but value should be set!

I have an abstract entity called Block which contains two attributes: column and order (which are not optional), and one relationship, thing, where it is the detail of a 1->M. I have another entity, Whatever, that has Block as its parent, and adds one attribute, someNumber.
My code looks like this:
Whatever *block = (Whatever *)[NSEntityDescription insertNewObjectForEntityForName:#"Whatever" inManagedObjectContext:managedObjectContext];
block.order = 0;
block.column = 0;
block.thing = self.thing;
When I try to save, I get this error:
Failed to save to data store: Operation could not be completed. (Cocoa error 1560.)
DetailedError: {
NSLocalizedDescription = "Operation could not be completed. (Cocoa error 1570.)";
NSValidationErrorKey = column;
NSValidationErrorObject = <Whatever: 0x5124890> (entity: someWhatever; id: 0x511b4e0 <x-coredata:///Whatever/t718B63A4-927B-4D88-A9E6-7F61CF9621675> ;
data: {
column = nil;
thing = 0x54367a0 <x-coredata://E6648244-E5FC-4202-B5F9-C7A91BACF8DA/Thing/p2>;
order = nil;
someNumber = 0;
});
I don't understand why it says that column and order are nil, as I've just set them the line before, so this shouldn't be a problem.
I've tried using the [block setColumn:0] style as well, without success.
Any help would be appreciated. Thanks!
You are setting them to nil since nil is just a null or zero pointer value.
Core Data properties must be set to objects (as opposed to primitive types).
Integers and floating point numbers are NSNumber objects.
I like to use the numberWith* convenience constructors.
For example:
block.order = [NSNumber numberWithInteger:0];
block.column = [NSNumber numberWithInteger:0];
To expand on gerry3's answer, a great way to ease coding with Core Data is to use Rentzsch's mogenerator. It would allow you to do:
block.orderValue = 0;
block.columnValue = 0;

Bulk update & occasional insert (coredata) - Too slow

Update: Currently looking into NSSET's minusSet
links: Comparing Two Arrays
Hi guys,
Could benefit from your wisdom here..
I'm using Coredata in my app, on first launch I download a data file and insert over 500 objects (each with 60 attributes) - fast, no problem.
Each subsequent launch I download an updated version of the file, from which I need to update all existing objects' attributes (except maybe 5 attributes) and create new ones for items which have been added to the downloaded file.
So, first launch I get 500 objects.. say a week later my file now contains 507 items..
I create two arrays, one for existing and one for downloaded.
NSArray *peopleArrayDownloaded = [CoreDataHelper getObjectsFromContext:#"person" :#"person_id" :YES :managedObjectContextPeopleTemp];
NSArray *peopleArrayExisting = [CoreDataHelper getObjectsFromContext:#"person" :#"person_id" :YES :managedObjectContextPeople];
If the count of each array is equal then I just do this:
NSUInteger index = 0;
if ([peopleArrayExisting count] == [peopleArrayDownloaded count]) {
NSLog(#"Number of people downloaded is same as the number of people existing");
for (person *existingPerson in peopleArrayExisting) {
person *tempPerson = [peopleArrayDownloaded objectAtIndex:index];
// NSLog(#"Updating id: %# with id: %#",existingPerson.person_id,tempPerson.person_id);
// I have 60 attributes which I to update on each object, is there a quicker way other than overwriting existing?
index++;
}
} else {
NSLog(#"Number of people downloaded is different to number of players existing");
So now comes the slow part.
I end up using this (which is tooooo slow):
NSLog(#"Need people added to the league");
for (person *tempPerson in peopeArrayDownloaded) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person_id = %#",tempPerson.person_id];
// NSLog(#"Searching for existing person, person_id: %#",existingPerson.person_id);
NSArray *filteredArray = [peopleArrayExisting filteredArrayUsingPredicate:predicate];
if ([filteredArray count] == 0) {
NSLog(#"Couldn't find an existing person in the downloaded file. Adding..");
person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:#"person" inManagedObjectContext:managedObjectContextPeople];
Is there a way to generate a new array of index items referring to the additional items in my downloaded file?
Incidentally, on my tableViews I'm using NSFetchedResultsController so updating attributes will call [cell setNeedsDisplay];
.. about 60 times per cell, not a good thing and it can crash the app.
Thanks for reading :)
I'll begin by saying that I'm still new to using the Core Data framework, but my guess is that your problem lies in the for loop you've posted.
If you look at your loop, each time it executes it creates a new NSPredicate object and then filters your existing array looking for matches. On a small data set this technique would work with seemingly small performance losses; however, with your large data set you will end up spending a lot of time creating NSPredicate objects that only differ in the name you've provided. I would suggest that you look at how to create a single predicate and then use variable substitution to perform the search. For information about variable use in predicates check out: http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174
As a side note, you may also consider how you've sorted your data and how you are performing the search operation. And another thing I noticed is that you don't release your NSPredicate object, so you're just tossing memory away too.