NSFetchRequest only returns results sometimes - ios

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.

Related

Updating Core Data fail

-(void)passDataBack:(PersonHolder *)contact{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstName == %#",contact.firstName];
[request setPredicate:predicate];
NSError *error;
Person *person = [[self.managedObjectContext executeFetchRequest:request error:&error]lastObject];
[person.firstName setValue:contact.firstName forKey:#"fistName"];
[person.lastName setValue:contact.lastName forKey:#"lastName"];
if(![self.managedObjectContext save:&error]){
NSLog(#"Could not save edited data");
}
}
during runtime I can see that contact is loaded with all the data I'm passing through but my person entity is not getting updated.
I saw the answer how to update core data entered values and I think I am certainly missing something
You don't missing something, quite the contrary
The Core Data equivalent to the simple property setter
person.firstName = contact.firstName
is
[person setValue:contact.firstName forKey:#"firstName"];
[person setValue:contact.lastName forKey:#"lastName"];
consider also the typo #"fistName"
Edit: Try this syntax
NSArray *people = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error) {
NSLog(#"%#", error);
return;
}
if (people.count) {
Person *person = [people lastObject];
person.firstName = contact.firstName;
person.lastName = contact.lastName;
if(![self.managedObjectContext save:&error]){
NSLog(#"Could not save edited data");
}
} else {
NSLog(#"result array is empty"):
}
Instead of
[person.firstName setValue:contact.firstName forKey:#"firstName"];
[person.lastName setValue:contact.lastName forKey:#"lastName"];
use
[person setValue:contact.firstName forKey:#"firstName"];
[person setValue:contact.lastName forKey:#"lastName"];
or
person.firstName = contact.firstName;
person.lastName = contact.lastName;
Edit : a suggestion before blindly assigning last object of fetch query to person object and setting values in core data, check if your fetch query returns a result or not. This may crash.
NSArray *persons = [[[CoreDataManager sharedManager] context] executeFetchRequest:fetchRequest error:&error];
if(persons.count > 0){
// do changes in the object
}
else{
// do what you want to do to handle this situation
}

Fetch objects from an entity of one to many relationship core data

I was scratching my head all the way through to get this problem solved my self but unfortunately i couldn't.
So my problem is i have multiple values corresponding to one lectures so i made lectureTpPbl,lectureToProfessor,lecturetoStudyProgram relation of type one to many relationship as shown in image.
i saved into the DB like below:
-(void)saveToDatabase:(NSMutableDictionary *)inData{
LectureDetails *lectureDetail = (LectureDetails *)[NSEntityDescription insertNewObjectForEntityForName:#"LectureDetails" inManagedObjectContext:context];
[lectureDetail setAverageRating:[inData valueForKey:kLectureAverageRating]];
[lectureDetail setCategoryID:[inData valueForKey:kLectureCategoryID]];
[lectureDetail setCategoryName:[inData valueForKey:kLectureCategoryName]];
[lectureDetail setLectureID:[inData valueForKey:kLectureID]];
[lectureDetail setSemesterID:[inData valueForKey:#"SemesterID"]];
[lectureDetail setStartTime:[inData valueForKey:kLectureStartTime]];
[lectureDetail setRatingType:[inData valueForKey:kRatingType]];
[lectureDetail setMonth:[NSNumber numberWithInt:[[[[inData valueForKey:kLectureDate] componentsSeparatedByString:#"-"] objectAtIndex:1] integerValue]]];
[lectureDetail setDateOnly:[NSNumber numberWithInt:[[[[inData valueForKey:kLectureDate] componentsSeparatedByString:#"-"] objectAtIndex:2] integerValue]]];
for (id prof in [inData valueForKey:#"Professors"]) {
ProfessorsInLecture *profData = (ProfessorsInLecture *)[NSEntityDescription insertNewObjectForEntityForName:#"ProfessorsInLecture" inManagedObjectContext:context];
profData.professorID=[NSNumber numberWithInt:[[prof valueForKey:#"ProfessorID"]integerValue]] ;
profData.professorFirstName=[prof valueForKey:#"ProfessorFirstName"];
profData.professorLastName=[prof valueForKey:#"ProfessorLastName"];
profData.ProfessorUserName=[prof valueForKey:#"ProfessorUserName"];
[lectureDetail addLectureToProfessorObject:profData];
}
for (id prog in [inData valueForKey:#"StudyPrograms"]) {
StudyProgramInLecture *stdyProgramData = (StudyProgramInLecture *)[NSEntityDescription insertNewObjectForEntityForName:#"StudyProgramInLecture" inManagedObjectContext:context];
stdyProgramData.sMID=[prog valueForKey:#"SMID"];
[lectureDetail addLectureToStudyProgramObject:stdyProgramData];
}
for (id pblData in [inData valueForKey:#"PBLs"]) {
PBLinLectures *pbl = (PBLinLectures *)[NSEntityDescription insertNewObjectForEntityForName:#"PBLinLectures" inManagedObjectContext:context];
[pbl setLecturePBLID:[pblData valueForKey:#"LecturePBLID"]];
[pbl setPBLID:[pblData valueForKey:#"PBLID"]];
[lectureDetail addLectureToPBLObject:pbl];
}
NSError *error1;
if (![context save:&error1])
{
//NSLog(#"ERROR--%#",error);
abort();
}
}
all the data is getting save correctly but i am getting problem in fetching the data.
Now suppose i have to fetch a lecture whose semesterID=7 and month=12 and day=20 and (in relation table)sMID=2 and (in relation table)pBLID=4.
what i have done so far is to fetch is
-(NSMutableArray *)fetchDataforMonth:(NSNumber *)month andDate:(NSNumber *)dateOnly
{
NSEntityDescription *entity = [NSEntityDescription entityForName:#"LectureDetails" inManagedObjectContext:context];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY lectureToPBL.pBLID ==%#", [NSNumber numberWithInt:[[[UserDetail sharedInstance].userDetail valueForKey:#"pBLID"]integerValue]]];
NSPredicate *pred1 =
[NSPredicate predicateWithFormat:#"(dateOnly == %#)",dateOnly];
NSPredicate *pred2 =
[NSPredicate predicateWithFormat:#"(month == %#)",month];
NSPredicate *pred3 =
[NSPredicate predicateWithFormat:#"(semesterID == %#)",[NSNumber numberWithInt:[[[UserDetail sharedInstance].userDetail valueForKey:#"semesterID"]integerValue]]];
NSPredicate *pred4 = [NSPredicate predicateWithFormat:#"ANY lectureToStudyProgram.sMID ==%#", [NSNumber numberWithInt:[[[UserDetail sharedInstance].userDetail valueForKey:#"programID"]integerValue]]];
NSArray *compPredicatesList = [NSArray arrayWithObjects:pred1,pred2,pred3,predicate,pred4, nil];
NSPredicate *CompPrediWithAnd = [NSCompoundPredicate andPredicateWithSubpredicates:compPredicatesList];
[request setPredicate: CompPrediWithAnd];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
return mutableFetchResults;
}
If there is one sMID and one pBLID then fetching is working fine otherwise i am not getting an object in fetch result.
Suppose i have a lecture whose semesterID is 7 and month=12 and day=20 and have multiples sMID such as 1,2,3 and multiples pBLID such as =4,5,6
and i want to fetch a lecture whose month=12 and day=20 and sMID=2 and pBLID=4
Can anybody tell me how to do that?
Any Help will be appreciated.
Thanks
First filter the Lecturers whose month=12 and day=20 from LectureDetails entity:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(month == %#) AND (dateOnly == %#) ",month, dateOnly];
[request setPredicate: pred];
NSError *error;
NSArray *lectureList = [context executeFetchRequest:request error:&error];
then filter this array with pBLID and sMID.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(ANY lectureToStudyProgram.sMID ==%#) AND (ANY lectureToStudyPBL.pBLID ==%#)", sMID, pBLID];
lectureList = [lectureList filteredArrayUsingPredicate:predicate];
Now lectureList will give you the result.

NSFetchRequest with Predicate not returning objects

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

How to update in Core Data?

I saw many questions about Core Data updates. Actually I am creating a simple application contact list app. It contains add, edit, delete and update functionalities. Here my update code. It works and updates, but it updates all the contact list. I need to update specific contacts only.
- (IBAction)updatePressed:(id)sender
{
delegate = [[AppDelegate alloc]init];
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
name2 = emailtxt1.text;
email2 = nametext1.text;
mobile2 = numbertxt1.text;
dict = [[NSMutableDictionary alloc] init];
[dict setObject:nametext1.text forKey:#"NAME"];
[dict setObject:emailtxt1.text forKey:#"EMAIL"];
[dict setObject:numbertxt1.text forKey:#"MOBILE"];
[delegate UpdateDiary:dict];
}
- (void)UpdateDiary:(NSMutableDictionary *)dictionary
{
NSLog(#"update book Details Function Entered");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Diary"inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *mutableFetchResult = [[[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy] autorelease];
if (mutableFetchResult == nil) {
NSLog(#"Fetch result error %#, %#", error, [error userInfo]);
}
for (Diary *ob2 in mutableFetchResult)
{
{
ob2.name = [dictionary objectForKey:#"NAME"];
ob2.email=[dictionary objectForKey:#"EMAIL"];
ob2.phone=[dictionary objectForKey:#"MOBILE"];
}
}
if(![self.managedObjectContext save:&error])
{
if([error localizedDescription] != nil)
{
NSLog(#"%#",error);
}
else
{
}
}
}
You need to set a predicate on your fetch request. That's how it knows which object(s) you want, rather than just fetching them all.
You could do something like:
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"email == %#", anEmailAddress];
If you did that, then the result of executing the fetch request would just be objects that matched the email address you set in the predicate.
Note, of course, that if there is more than one object with the same email address, then the fetch request would fetch all of them.
A better design for your app might be, when you go into the edit form, keep around the Core Data object that you're editing, possibly in a property on your view controller. (You'll have it around at that point I reckon, since you'll need to know what to populate the fields with.) That way you don't need to perform a fetch at the time the user is trying to commit the edit — you can just use the object you've kept around.
- (void)UpdateBook:(NSMutableDictionary *)dictionary
{
NSLog(#"update book Details Function Entered");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Book"inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"bookID = %#", [dictionary objectForKey:#"vID"]];
NSArray *mutableFetchResult = [[[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy] autorelease];
if (mutableFetchResult == nil) {
NSLog(#"Fetch result error %#, %#", error, [error userInfo]);
}
for (Book *ob2 in mutableFetchResult)
{
{
ob2.name = [dictionary objectForKey:#"VName1"];
ob2.author=[dictionary objectForKey:#"VAuthor1"];
ob2.discription=[dictionary objectForKey:#"VDiscription1"];
ob2.bookID=[dictionary objectForKey:#"vID"];
}
}
if(![self.managedObjectContext save:&error])
{
if([error localizedDescription] != nil)
{
NSLog(#"%#",error);
}
else
{
}
}
}

NSPredicate Returns No Results with Fetch Request, Works with Array Filtering

My situation is simple: I have some records in my core data store. One of their attributes is a string called "localId". There's a point where I'd like to find the record with a particular localId value. The obvious way to do this is with an NSFetchRequest and an NSPredicate. However, when I set this up, the request returns zero records.
If, however, I use the fetch request without the predicate, returning all records, and just loop over them looking for the target localId value, I do find the record I'm looking for. In other words, the record is there, but the fetch request can't find it.
My other methods in which I use fetch requests and predicates are all working as expected. I don't know why this one is failing.
I want to do this:
- (void)deleteResultWithLocalID:(NSString *)localId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAAssessmentResult" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"localId == %#", localId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSAssert(error == nil, ([NSString stringWithFormat:#"Error: %#", error]));
if ([results count]) [context deleteObject:[results objectAtIndex:0]];
else NSLog(#"could not find record with localID %#", localId);
[self saveContext];
}
But I end up having to do this:
- (void)deleteResultWithLocalID:(NSString *)localId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAAssessmentResult" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSAssert(error == nil, ([NSString stringWithFormat:#"Error: %#", error]));
for (WCAAssessmentResult *result in results) {
if ([result.localId isEqualToString:localId]) {
NSLog(#"result found, deleted");
[context deleteObject:result];
}
}
[self saveContext];
}
Any clues as to what could be going wrong?
edit
I've found that I can use the predicate I'm creating to get the results I expect after the fetch request has been executed. So, the following also works:
- (WCAChecklistItem *)checklistItemWithId:(NSNumber *)itemId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAChecklistItem" inManagedObjectContext:context]];
NSArray *foundRecords = [context executeFetchRequest:request error:nil];
foundRecords = [foundRecords filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"serverId == %#", itemId]];
if ([foundRecords count]) {
return [foundRecords objectAtIndex:0];
} else {
NSLog(#"can't find checklist item with id %#", itemId);
return nil;
}
}
UPDATE
I've come across someone else experiencing this very issue:
http://markmail.org/message/7zbcxlaqcgtttqo4
He hasn't found a solution either.
Blimey! I'm stumped.
Thanks!
If localId is a numerical value, then you should use an NSNumber object in the predicate formation instead of an NSString.
[request setPredicate:[NSPredicate predicateWithFormat:#"localId == %#",
[NSNumber numberWithString:localId]]];
NSPredicate format strings automatically quote NSString objects.
Hate to say it but the most common cause of these types of problems is simple typos. Make sure that your attribute names and the predicate are the same. Make sure that your property names and the attributes names are the same. If a reference to a property works but a reference to attribute name doesn't there is probably a mismatch.
You could test for the latter by comparing the return of:
[result valueForKey:#"localID"]
... with the return of:
result.localID

Resources