Updating Core Data fail - ios

-(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
}

Related

Core Data: How to get data of entity in relationship?

I have some data in Database. There's a Person and his Addresses as One-to-Many relationship, saved like this:
// Create Person
NSEntityDescription *entityPerson = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
NSManagedObject *newPerson = [[NSManagedObject alloc] initWithEntity:entityPerson insertIntoManagedObjectContext:self.managedObjectContext];
// Set First and Last Name
[newPerson setValue:#"Bart" forKey:#"first"];
[newPerson setValue:#"Jacobs" forKey:#"last"];
[newPerson setValue:#44 forKey:#"age"];
// Create Address
NSEntityDescription *entityAddress = [NSEntityDescription entityForName:#"Address" inManagedObjectContext:self.managedObjectContext];
NSManagedObject *newAddress = [[NSManagedObject alloc] initWithEntity:entityAddress insertIntoManagedObjectContext:self.managedObjectContext];
// Set info
[newAddress setValue:#"Main Street" forKey:#"street"];
[newAddress setValue:#"Boston" forKey:#"city"];
// Add Address to Person
[newPerson setValue:[NSSet setWithObject:newAddress] forKey:#"addresses"];
// Create Address
NSManagedObject *otherAddress = [[NSManagedObject alloc] initWithEntity:entityAddress insertIntoManagedObjectContext:self.managedObjectContext];
// Set info
[otherAddress setValue:#"5th Avenue" forKey:#"street"];
[otherAddress setValue:#"New York" forKey:#"city"];
// Add Address to Person
NSMutableSet *addresses = [newPerson mutableSetValueForKey:#"addresses"];
[addresses addObject:otherAddress];
// Save Managed Object Context
NSError *error = nil;
if (![newPerson.managedObjectContext save:&error]) {
NSLog(#"Unable to save managed object context.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
Now I need to fetch this person. I can fetch all of his attributes, but how should I get his addresses and attributes of them?
My code so far:
// Fetching
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Person"];
// Execute Fetch Request
NSError *fetchError = nil;
NSArray *result = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
if (!fetchError) {
for (NSManagedObject *managedObject in result) {
NSLog(#"%#, %#", [managedObject valueForKey:#"first"], [managedObject valueForKey:#"last"]);
// HERE I WANT TO PRINT INFO ABOUT HIS ADDRESSES
}
} else {
NSLog(#"Error fetching data.");
NSLog(#"%#, %#", fetchError, fetchError.localizedDescription);
}
Just fetch the Person. Fetching the attributes will be done for you in an optimized fashion once you need the attributes. You simply access the attributes (assume they are already there).
NSString *message = [NSString stringWithFormat:#"Hello, %#.", person.first];
If you need the addresses, you get them with
NSSet *addresses = person.addresses;
They are in no particular order, but you can sort them with sortedArrayUsingDescriptors and similar methods. In any case, you do not need another fetch request.
BTW, your way to set the relationship is unnecessarily complicated and error prone. If one side of the relationship is to-one, use it to define the relationship:
newAddress.person = person;

NSFetchRequest only returns results sometimes

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.

IOS Core Data - Find or Create duplicates inserts

I'm missing something in my logic when trying to sync web service data with local store and I need your help. This is what I've got:
I have one NSArray of NSDictionaries describing each event object (downloaded from web), which I sort by event id. Then I fetch local store using IN predicate and also sort it by event id. Then I try to iterate and match the ids and if they match, i update record and if they don't match i create new NSManagedObject. It works fine if the newly downloaded event object has a greater eventID than last eventID in local store, but if the eventID from web service is smaller than the one in local store then it INSERTS ALL OBJECTS, no matter if they exist or not and that exactly is my problem.
So in other words, if a new record is at the beginning of sorted array it will add every object, but if it is at the end of sorted array it will update all except the new one. I need it to create the new one and update old ones.
Here's some code:
The function with the logic where I believe I'm missing something:
- (void)findOrCreateObject:(NSArray*)eventArray
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
//get sorted stored records
NSArray *fetchedRecords = [self.fetchedResultsController fetchedObjects];
//sort dictionaries
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"id" ascending:YES];
NSArray *downloadedRecords = [self.events sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
NSLog(#"DOWNLOADED EVENTS = %#", downloadedRecords);
NSLog(#"FETCHED EVENTS = %#", fetchedRecords);
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
NSUInteger currentIndex = 0;
for (NSDictionary* event in downloadedRecords) {
Event* eventObject = nil;
if ([storedRecords count] > currentIndex) {
eventObject = [storedRecords objectAtIndex:currentIndex];
}
NSLog(#"STRING VALUE OF KEY = %#", [[eventObject valueForKey:#"eventID"]stringValue]);
if ([[event valueForKey:#"id"] isEqualToString:[[eventObject valueForKey:#"eventID"] stringValue]]) {
//Update Record
NSLog(#"Updating Record!!!");
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
currentIndex++;
}
}
}
else
{
//import initial data
NSLog(#"IMPORTING INITIAL DATA");
for (NSDictionary* event in downloadedRecords) {
Event *eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
The FETCHEVENTS method:
-(NSArray*)fetchEvents:(NSArray*)eIDs withContext:(NSManagedObjectContext*)context
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eventID IN %#)", eIDs];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:#[ [[NSSortDescriptor alloc] initWithKey: #"eventID" ascending:YES] ]];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"No rows returned");
}
return fetchedObjects;
}
The Update Object method:
- (void)updateManagedObject:(NSManagedObject*)object withRecord:(NSDictionary*)record inContext:(NSManagedObjectContext*)context
{
[object setValue:[self makeNumberFromString:[record valueForKey:#"id"]] forKey:#"eventID"];
[object setValue:[record valueForKey:#"title"] forKey:#"title"];
[object setValue:[record valueForKey:#"venue"] forKey:#"venue"];
}
I'm calling findOrCreate method once I download the web service data and parse it.
Let me know if you have any other questions.
Try this,
- (void)findOrCreateObject:(NSArray*)eventArray {
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
for (NSDictionary* event in downloadedRecords) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"eventID = %#",[event valueForKey:#"id"]];
NSArray *matchedArray = [storedRecords filteredArrayUsing
Predicate:predicate];
Event* eventObject = nil;
if ([matchedArray count] > 0) {
//Update Record
NSLog(#"Updating Record!!!");
eventObject = [matchedArray objectAtIndex:0];
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
}
} else {
.....
}
}
I think, every time you insert a new event object, you should update storedObjects such that it should now contain the inserted object.
Or more simply, you should put the initialisation line of storedObjects inside your for loop. (This would make sure that as you enumerate from the beginning of downloadedObjects every eventObject will have the same index on it as on storedObjects. But, with regards to your code this will only be true if all elements of storedObjects will always be found in downloadedObjects which, I assume is the case.)
One thing though, isn't fetchedRecords supposed to be the same as storedObjects, if they are you should just reassign storedObjects as [self.fetchedResultsController fetchedObjects], as it would reflect the changes in your context without executing another fetch request which would solve the inefficiency of the suggestion above.

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