In my Delegate I specify the following method for retrieving an NSSet of NSManagedObjects:
- (NSSet *) entitiesForName : (NSString *)entityName matchingAttributes : (NSDictionary *)attributes {
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext: [NSThread isMainThread] ? managedObjectContext : bgManagedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity: entity];
NSMutableArray *subPredicates = [[NSMutableArray alloc] init];
[attributes enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
if ([value class] == [NSString class]) {
NSString *sValue = (NSString *)value;
[subPredicates addObject:[NSPredicate predicateWithFormat:#"%# == '%#'", key, [sValue stringByReplacingOccurrencesOfString:#"'" withString:#"\\'"]]];
} else {
[subPredicates addObject:[NSPredicate predicateWithFormat:#"%# == %#", key, value]];
}
}];
NSPredicate *matchAttributes = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
NSLog(#"matchPredicate: %#", [matchAttributes description]);
[fetch setPredicate: matchAttributes];
NSError *error;
NSSet *entities = [NSSet setWithArray: [managedObjectContext executeFetchRequest:fetch error:&error]];
if (error != nil) {
NSLog(#"Failed to get %# objects: %#", entityName, [error localizedDescription]);
return nil;
}
return [entities count] > 0 ? entities : nil;
}
I then initiate this method using an Entity I know exists and matching an attribute I know has some of the same value (I checked the sqlite file):
[self entitiesForName:#"Lecture" matchingAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:#"attending"]]
The console outputs the following (showing the predicate):
2013-09-11 22:47:20.098 CoreDataApp[1442:907] matchPredicate: "attending" == 0
Info on the NSObject Entity:
- property "attending" is a BOOL (translated to NSNumber in class)
- There are many records in this table (Lecture entity), half with "attending" value 0 and other half 1
- Using the method -entitiesForName above, it returns an empty set
Other Info:
I have another method defined to retrieve the same way, but without the predicate (retrieves all managedobjects) and this works going from the same table. I used this, and analysing the retrieved records from this also proves that some have "attending" 0 and some 1
Question:
Is there something wrong with my -entitiesForName method that would cause the set to come back empty?
You should not use %# for the key - you need to use %K for the key path.
For example
[NSPredicate predicateWithFormat:#"%K == %#", key, value]
You can find more info in the Predicate Programming Guide
In your current case the key is being treated as a string
Related
I'm answering my own question on this one. I won't accept it until I see as much other answers as possible.
For the past couple of days I've been struggling with the below code
- (id)fetchUniqueEntity:(Class)entityClass
withValue:(id)value
fromContext:(NSManagedObjectContext *)context
insertIfNil:(BOOL)insertIfNil {
if(!value) {
return nil;
}
NSString *entityUniqueIdentifierKey = [entityClass performSelector:#selector(uniqueIdentifierKey)];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", entityUniqueIdentifierKey, value];
NSArray *entities = [self fetchEntities:entityClass
withPredicate:predicate
fromContext:context];
if(!entities || [entities count] == 0) {
if (insertIfNil) {
return [entityClass performSelector:#selector(insertInManagedObjectContext:)
withObject:context];
}
} else {
return [entities objectAtIndex:0];
}
return nil;
}
- (NSArray *)fetchEntities:(Class)entityClass
withPredicate:(NSPredicate *)predicate
fromContext:(NSManagedObjectContext *)context {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSString *entityName = [entityClass performSelector:#selector(entityName)];
[request setEntity:[NSEntityDescription entityForName:entityName
inManagedObjectContext:context]];
if(predicate) {
[request setPredicate:predicate];
}
NSError *error;
NSArray *entities = [context executeFetchRequest:request
error:&error];
if(error) {
NSLog(#"Error executing fetch request for %#! \n\n%#", entityName, error);
}
return entities;
}
The top helper method is meant to help establish relationships during a large download. The problem I was having is that, even if say there was an Account object with an accountId of 1 in the persistent store (confirmed), it was only retrieving that object it half the time. I honestly cannot explain why. Any ideas?
After much debugging and trial and error, I've discovered that changing the predicate format from
[NSPredicate predicateWithFormat:#"%K == %#", entityUniqueIdentifierKey, value];
to
[NSPredicate predicateWithFormat:#"%K == %d", entityUniqueIdentifierKey, [value intValue]];
solved the problem. After studying the apple documentation, it seems that %# should work. So I am not 100% sure why this solved the problem.
Contact entity has a many to one relationship to user, which has a userID field
Contact has a status string attribute that can be either 'approved', 'rejected', 'pending' etc
favorite boolean attributes
+ (NSSet *)fetchContactsWithUserID:(NSString *)userID approvedOnly:(BOOL)approvedOnly favoriateOnly:(BOOL)favoriateOnly {
NSString *predString = [NSString stringWithFormat:#"(user.userID = %#)", userID];
if (approvedOnly) {
predString = [predString stringByAppendingString:#" AND (status = approved)"];
}
if (favoriateOnly) {
predString = [predString stringByAppendingString:#" AND (favorite = YES)"];
}
NSPredicate *pred = [NSPredicate predicateWithFormat:predString];
return [self private_fetchEntitiesWithName:#"Contact" predicate:pred];
}
+ (NSSet *)private_fetchEntitiesWithName:(NSString *)name predicate:(NSPredicate *)predicate {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:name inManagedObjectContext:[self private_context]];
[fetchRequest setEntity:entity];
if (predicate) {
[fetchRequest setPredicate:predicate];
}
NSError *error;
NSArray *fetchedObjects = [[self private_context] executeFetchRequest:fetchRequest error:&error]; //CRASH ON THIS LINE!!!!!!
return [NSSet setWithArray:fetchedObjects];
}
stack trace:
There is no error logged out (i already enabled nszombie)
#"(status = approved)" is not a valid predicate. If approved is a string your predicate string should be #" AND (status = \"approved\")" or #" AND (status = 'approved')".
If you use a string literal directly in a predicate you have to enclose it in quotes.
Strings that are not enclosed in quotes are treated as keys by the NSFetchRequest.
This is done because you can actually check if two attributes are equal by using a predicate that compares those two, e.g.#"attribute1 = attribute2".
If you use a predicate format that contains %#, NSPredicate will automatically add quotes if the object you want to use as argument is a NSString.
NSPredicate *p1 = [NSPredicate predicateWithFormat:#"foo = %#", #"Bar"];
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"foo = Bar"];
NSLog(#"%#", p1);
NSLog(#"%#", p2);
yields:
xxx[4673:70b] foo == "Bar"
xxx[4673:70b] foo == Bar
I am using following fetch request to delete core data objects:
NSEntityDescription *entity=[NSEntityDescription entityForName:#"entityName" inManagedObjectContext:context];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
[fetch setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(value1 == %#) AND (value2 == %#)", data1, data2];
[fetch setPredicate:predicate];
//... add sorts if you want them
NSError *fetchError;
NSArray *fetchedData=[self.moc executeFetchRequest:fetch error:&fetchError];
for (NSManagedObject *product in fetchedProducts) {
[context deleteObject:product];
}
What I need is to execute the fetch request only if the number of objects in the core data entity with [value1 isEqualToString: #"borrar"] is greater than 1. How could I add this condition?
***EDIT
The attribute value1 is a transient attribute.
To check how many objects with a given attribute value exist, use countForFetchRequest::
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"entityName"];
[request setPredicate:[NSPredicate predicateWithFormat:#"value1 = %#", #"borrar"]];
NSError *error;
NSUInteger count = [self.moc countForFetchRequest:request error:&error];
if (count == NSNotFound) {
// some error occurred
} else if (count > 1) {
// more the one object with "value1 == borrar"
}
Update (according to the edited question): You cannot use a transient attribute
in a Core Data fetch request. If "value1" is a transient attribute, you can only
fetch all objects with "value2 == something", and then iterate over the fetched
array to check if there is more than one object with "value1 == borrar".
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"(Value1 == borrar)"];
[fetch setPredicate:predicate];
NSError *fetchError;
NSArray *fetchedData=[self.moc executeFetchRequest:fetch error:&fetchError];
for(int i=0;i<fechedData.count;i++){
[context deleteObject:[fechedData objectAtIndex:i]valueForKey:#"Value1"];
}
I have the following scenario. I have an app that handles data by using Core Data. I have an entity called "Brothers" which has 3 attributes: Name, Status, Age.
lets say that I have 1 record on my database that has:
-Name ==> Joe (String)
-Status ==> Married (String)
-Age ==> 28 (Integer)
I have an UIlabel a UItextfield and a Button. I want to be able to type the name (in this case Joe) on the UItextfield press the button and display the age (in this case 28) in the UILabel. I would also like to store the age value into a variable type integer so I can make some calculations with it later on.
below the code that I have inside the button.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Brothers" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age %d", [self.age.text integerValue]];
[request setPredicate:predicate];
NSError *error;
NSArray *integer = [context executeFetchRequest:request error:&error];
self.displayLabel.text = integer;
Update #1
I updated the code that i have inside the button and now i am able to search by the name and display the age. I'm still looking into storing the age as an integer into a variable so i can use it later on.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Brothers" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstname like %#", self.firstnameTextField.text];
[request setPredicate:predicate];
NSError *error;
NSArray *integer = [context executeFetchRequest:request error:&error];
if(integer.count <= 0){
self.displayLabel.text = #"No records found";
}
else {
NSString *age;
for (NSManagedObject *object in integer) {
age = [object valueForKey:#"age"];
}
self.displayLabel.text = [NSString stringWithFormat:#"age: %#",age];
}
}
A predicate is an expression. If the expression evaluates to true then the predicate is satisfied. So if you were searching by age you'd use e.g.
[NSPredicate predicateWithFormat:#"age = %d", [self.age.text integerValue]]
Or by name:
[NSPredicate predicateWithFormat:#"name = %#", someNameOrOther]
Or by both:
[NSPredicate predicateWithFormat:#"(name = %#) and (age = %d)", someNameOrOther, [self.age.text integerValue]]
A fetch request gets the actual NSManagedObjects. So you'd get back an array of Brothers. Therefore you probably want something more like this to output a name:
if([array count])
self.displayLabel.text = [array[0] name];
Or for an age:
...
self.displayLabel.text = [[array[0] age] stringValue];
Or whatever other property you're outputting.
I think your predicate is wrong, the proper format would be this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age.integerValue == %d", [self.age.text integerValue]];
I'm using a UICollectionView along with Core Data.
I can't seem to figure out why I can't remove an object from Core Data. It crashes at [self.managedObjectContext deleteObject:animation]; with an index 0 beyond bounds for empty array error. The following method is a delegate method that gets invoked from another viewcontroller. It gets a timestamp NSInteger and should create an NSPredicate with that timestamp.
The strange thing is that when I take the animationTimestamp NSInteger and hardcode it like:
[NSPredicate predicateWithFormat:#"timestamp == %i", #"1370169109"] it works and the object gets deleted without the error. However, when I want to pass the parameter as the argument it crashes. I've tried making it a string, NSNumber etc. Nothing works when I take the parameter, it only does when I hardcode it. I've logged animationTimestamp and it shows the correct value, so I'm stuck.
Thanks!
- (void)deleteAnimationWithTimestamp:(NSInteger)animationTimestamp {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Animations" inManagedObjectContext:self.managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"timestamp == %i", animationTimestamp];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *animations = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *animation in animations) {
[self.managedObjectContext deleteObject:animation];
}
// Save core data
if (![self.managedObjectContext save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
[self.animationsCollectionView reloadData];
}
EDIT:
More info that might matter. The timestamp attribute in the model is a 64 bit integer (should it be by the way?). When I create the object I use:
NSNumber *timestampNumber = [NSNumber numberWithInt:timestamp];
[newAnimation setValue:timestampNumber forKey:#"timestamp"];
Get the NSInteger value from NSNumber
Like
NSInteger timestampNumber = [[NSNumber numberWithInt:timestamp] integerValue];
// then use this integer
[newAnimation setValue:timestampNumber forKey:#"timestamp"];
// or in
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"timestamp == %d", timestampNumber];