CoreData NSManagedObject isn't being updated - ios

I'm trying to update an existing object in my Core Data stack,
The object is being retrieved and i can change the value, but when saving the context , nothing is being saved. also the hasChanges is NO and updatedObjects is empty.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: #"MyModel"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(x == %#) AND (y == %#)", x, y];
request.returnsObjectsAsFaults = NO;
request.predicate = predicate;
NSArray *results = [self.managedObjectContext executeFetchRequest: request error: nil];
MyModel *model = results[0];
model.newvalue = "somenewvalue";
NSLog(#"%#", (self.managedObjectContext.hasChanges)?#"YES":#"NO"); // -> Shows NO
NSLog(#"%#", self.managedObjectContext.updatedObjects); // -> Shows empty array
[self saveContext]; // No error, seems to be working successfully but the updated objects is empty as well has "hasChanges", so something else is being missed here
This is a bit baffling and annoying. I would really appreciate any insights on this issue.
As suggested this was tried as well but didn't help
NSManagedObject *model = results[0];
[model setValue: #"somenewvalue" forKey: #"newvalue"];

Crazy answer but I can definitely imagine someone else making this error.
Inside my NSManagedObject instead of #dynamic for each property I had #synthesize. That was the issue.

Related

core data relationship, how to insert and retrieve data from relationships

I am new in IOS development and I am having a problem when using core data specially when using relationships
I have the following relationship
enter image description here
as you see i have tree entities and what i am trying to do is insert into CredentialsFood entity the data when food is consumed by a particular user
so I am trying to insert using the following code
// adding to credetialsFood Entity
NSManagedObjectContext *context1 = [appD managedObjectContext];
CredentialsFood *credentialsFood = (CredentialsFood *) [NSEntityDescription insertNewObjectForEntityForName:#"CredentialsFood" inManagedObjectContext:context1];
NSDate *currentDate = [NSDate date];
credentialsFood.toFood.food_id = item_id;
credentialsFood.toCred.email = #"123";
credentialsFood.date = currentDate;
// here's where the actual save happens,
if(![context1 save:&errorCoreData]){
NSLog(#"Problem saving: %#",[error localizedDescription]);
}
else{
NSLog(#"Food date Saved");
}
the code works fine
however when retrieving I try for example get all CredentialsFood.date where Credentials.email = 123
I receive nothing
I don't really know What i am doing wrong this is the code to retrieve
NSManagedObjectContext *context2 = [appD managedObjectContext];
NSEntityDescription *foodEntity =[NSEntityDescription entityForName:#"Credentials" inManagedObjectContext:context2];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSError *error2 = nil;
NSString * userid =#"123";
NSPredicate *foodPredicate = [NSPredicate predicateWithFormat:#"(email contains[cd] %#)",userid];
[request setEntity:foodEntity];
[request setPredicate:foodPredicate];
NSManagedObject *matches = nil;
NSArray *objects = [context executeFetchRequest:request error:&error2];
if([objects count]== 0){
NSLog (#"no matches found");
}
else {
//NSLog(#"aasdasdad %#", objects);
matches = [objects objectAtIndex:0];
NSLog(#"bb %#",[matches valueForKey:#"name"]);
NSLog(#"bb %#",[matches valueForKey:#"email"]);
NSLog(#"bb %#",[matches valueForKey:#"password"]);
//NSLog(#"hhhhhhhhhhhhhh %#",[matches valueForKey:#"credentails"]);
NSMutableSet *query = [matches mutableSetValueForKey:#"credentails"];
NSArray *financialData =[matches valueForKeyPath:#"credentails"];
NSLog(#"%#",financialData);
NSLog(#"%#",[financialData valueForKey:#"date"]);
the data is inside core data however i don't know if it is performing the relation, the Food entity is populated and the CredentialsFood is also populated
I also getting the warnings
abstract entity Food has no children
abstract entity Credentials has no children
abstract entity CredentialsFood has no children
Thanks in advance
When you first create the object:
CredentialsFood *credentialsFood = (CredentialsFood *) [NSEntityDescription insertNewObjectForEntityForName:#"CredentialsFood" inManagedObjectContext:context1];
NSDate *currentDate = [NSDate date];
credentialsFood.toFood.food_id = item_id;
credentialsFood.toCred.email = #"123";
...you do not appear to be assigning anything to the toFood or toCred relationships. Related objects are not created automatically, so those properties will be nil. In Objective-C it's not an error to run this code while those properties are nil, but the code has no effect. It looks like you need to create (or fetch) an instance of Food and of Credentials, and assign those new objects to the toFood and toCred attributes.

Core Data: Expression tree is too large

I need some help with this method:
// deletes all points in database except points with given identifiers
- (void)deleteAllPointsExcept:(NSArray *)safeIdentifiers save:(BOOL)save {
// create request to fetch all 'doomed' points
NSManagedObjectContext *context = [[TSAppDelegate delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"MapPoint"];
NSMutableArray *subpredicates = [NSMutableArray array];
for (NSNumber *identifier in safeIdentifiers) {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"pid!=%#", identifier];
[subpredicates addObject:pred];
}
fetchRequest.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
// fetch 'em & destroy 'em
NSError *error;
NSArray *points = [context executeFetchRequest:fetchRequest error:&error];
for (TSMapPoint *point in points) {
[context deleteObject:point];
}
// save if requested
if (save) {
[[[TSAppDelegate delegate] managedObjectContext] save:NULL];
}
}
It's supposed to purge unneeded MapPoints from my Core Data NSManagedObjectContext. It works fine but one day I've received this message:
CoreData: error: (1) I/O error for database at ... SQLite error code:1, 'Expression tree is too large (maximum depth 1000)'
I've googled that this happens when the predicate is too long like WHERE id=1 OR id=2 OR id=3 ... but I don't know how to work around this. Does anybody have an idea?
Thanks in advance,
Pete.
Your predicate should be simplified to something like the following
[NSPredicate predicateWithFormat:#"NOT (pid IN %#)", safeIdentifiers];
When you have too many identifiers, you cannot use them directly in an SQL command (regardless of whether you're using parameters or not).
You have to write all the IDs into a temporary table, and then use a single predicate that checks that table:
DELETE FROM PointsTable WHERE pid NOT IN MyTempTable

Edit CoreData object then save context

I have two entities, one called InProject that has several attributes and one relationship. the relationship is with another entity called Ins.
I am editing one of the Ins that is related to InProject. I used InProject attribute ID which then returns a NSDictionary value that has several key-values one of which is for an array of Ins. I then find the Ins I need to edit in a for loop I edit them, but then I become unstuck because I am not sure how to save the contect of InProject with the *updated Ins
I need to figure out how to save InProject after I have overwritten the Ins attributes I need to update.
This is what my code looks like after battling this problem:
- (void)editSelectedins:(NSString *)projIDString UpdatedNSD:(NSMutableDictionary *)updatedNSD DPC:(int)dpc{
// get context
NSManagedObjectContext *context = [self managedObjectContext];
if (context == nil) {
NSLog(#"Nil");
}
else {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *InsProjectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (InsProject *insProj in fetchedObjects) {
NSMutableDictionary *tempInsProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempInsProjectDictionaryArray setObject:insProj.companyName forKey:#"CompanyName"];
[tempInsProjectDictionaryArray setObject:insProj.projNo forKey:#"ProjNo"];
[tempInsProjectDictionaryArray setObject:insProj.desc forKey:#"Desc"];
[tempInsProjectDictionaryArray setObject:insProj.guid forKey:#"GUID"];
[tempInsProjectDictionaryArray setObject:insProj.projID forKey:#"ProjID"];
[tempInsProjectDictionaryArray setObject:insProj.ins forKey:#"ins"];
[InsProjectDictionaryArray addObject:tempInsProjectDictionaryArray];
}
// now that you have the InsProjects, choose the one you are curently working on in insView using the projectID
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
[fetchRequest setPredicate:predicate];
// new array with one value that was created using the NSPredicate ProjID
NSArray *tempInsProjectArray = [InsProjectDictionaryArray filteredArrayUsingPredicate:predicate];
// get ins array out of the NSDictionary to edit
NSSet *inssForInsProject = tempInsProjectArray[0][#"ins"];
NSMutableArray *tempAllinss = [[NSMutableArray alloc] init]; // this will contain everything, that means all repeated values are included
for (Items* currItem in [inssForInsProject allObjects]) {
NSArray *keys = [[[currItem entity] attributesByName] allKeys];
NSDictionary *dict = [currItem dictionaryWithValuesForKeys:keys];
[tempAllinss addObject:dict];
}
NSArray *myArray = [tempAllinss copy];
// get the correct items from myArray anything whos dpc matches the dpc parameter of this method
NSMutableArray *editedinsArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [myArray count]; i++) {
NSMutableDictionary *tempinssDictionary = [myArray objectAtIndex:i];
// if you get a match put it into the new editedinsArray to be edited
if ([[tempinssDictionary objectForKey:#"dpc"] integerValue] == dpc) {
[editedinsArray addObject:tempinssDictionary];
}
}
// by now you should have three things
// 1, access to your ins coredata object //this s wrong I actually have access to insProject
// 2, the values you need to be edited saved into a NSArray (editedinsArray, which will be used to check against and keep old values correct)
// 3, UpdatedNSD which will be used to update any values that need to be updated.
// go through your values and update the ins object
int i = 0;
for (ins *temp in editedinsArray) {
NSDictionary *currentEditedins = [editedinsArray objectAtIndex:i];
i++;
// these values should stay the same so use currentEditedins which contains old vals
NSString *stringToNumberDpc = [currentEditedins valueForKey:#"dpc"];
int tempDpcNum = [stringToNumberDpc integerValue];
NSNumber *dpcNumber = [NSNumber numberWithInt:tempDpcNum];
temp.dpc = dpcNumber;
NSString *totDQtyString = [currentEditedins valueForKey:#"totDQty"];
if ((NSNull *)totDQtyString == [NSNull null]) {
temp.totDQty = #"";
} else {
temp.totDQty = totDQtyString;
}
NSString *totShipString = [currentEditedins valueForKey:#"totShip"];
if ((NSNull *)totShipString == [NSNull null]) {
temp.totShip = #"";
} else {
temp.totShip = totShipString;
}
// values to be updated so use updatedNSD wthich was passed in as method param with the new vals
temp.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
temp.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
temp.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
#warning --- I have no idea what to do here... i.e. how do I update the tempInsProjectArray.ins values I have just updated in the above for loop then save context which I hope would update insProj and the ins entities involved.
//save
[context save:&error];
}
}
As you can see at the bottom of the code with #warning I explain where I am having the issue. if I log temp inside the for loop I see the updated values perfectly the issue I am having is how do I then update the current tempInsProjectArray.ins values that I have just edited? then save them of course.
Your code is in great need of simplification. Some ground rules:
Use names with smallInitial and camelCase for variables. So not InsProjectDictionaryArray but insProjectDictionaryArray.
The same applies to dictionary keys indicating attribute names of managed objects. So projNo, not ProjNo.
Avoid cryptic abbreviations. Use plain and readable English Not projNo but projectNumber. What is an Ins? What is "dcp"?
Don't use the plural form for entity names. An suitable name for an item is Item, not Items
Don't use the mutable versions of dictionary and array when immutable ones would do.
Avoid duplicating your data, such as in [array copy].
Avoid dictionaries when you have an object graph. The object graph is what core data creates. It renders dictionaries with values and keys unnecessary.
Don't use IDs. The object graph renders those unnecessary as well in most cases. If you use IDs, do not use strings but numbers, such as long ints, or the object version NSNumber.
When fetching data from the Core Data persistent store, don't fetch all the data and the filter the result. Fetch only the data you need.
What you want to accomplish can surely be done in a few lines of code. I will try to summarize what you want to do as far as I understand it.
Your data model looks something like this:
Project <----->> Item
Where the items are in a to-many relationship called ins. I will rename this items. I will also assume that you will refactor your IDs to be of type NSNumber.
All the code up to myArray could be substituted with this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:"Project"];
request.predicate = [NSPredicate predicateWithFormat:#"projectID = %#", projectID];
request.fetchLimit = 1;
NSArray *fetchedObjects = [self.managedObjectContext
executeFetchRequest:request error:nil];
Project *project = fetchedObjects[0];
You now have all items available simply with project.items. I understand that there could be more than one item with a mysterious attribute dcp of type int (i.e. NSNumber for managed objects), that is equal to the dcp parameter passed.
NSSet *matchingItems = [project.items filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:#"dcp = %#", #(dcp)]];
Now it becomes a bit murky. Why do you have type ins in your for loop if the ins are actually of type Item? You then cast them into a dictionary... This should generate a compiler error. Or you have another class called ins instead of Ins??
Anyway, if you stay with the Items you can just update the values with what you pass in your dictionary:
for (Item *item in matchingItems) {
item.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
item.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
item.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
[self.managedObjectContext save:nil];
Done!
BTW you could make it even shorter by setting the entity name of the fetch request to "Item" and setting the following predicate:
[NSPredicate predicateWithFormat:#"project.projectID = %# && dcp = %#",
projectID, #(dcp)];
If you know your InProject, then updating your Ins related to that project is a matter of editing property values on your managed objects.
Why not use the predicate to get an NSManagedObject of the InProject, then pull the relationship off of that and edit the values?
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
return;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the predicate on the Core Data fetch request instead
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// We now have an array that has objects matching the projectIdString
// Might want to do some additional checks if you're only expecting zero or one objects
InsProject *aProject = [fetchedObjects lastObject];
// If we have no project, no point going any further
if ( !aProject ) return;
// On this NSManagedObject is an NSSet property with all related Ins objects
for ( Ins *anIns in aProject.ins ) {
// If our Ins item matches the passed dpc...
if ( [ins.dpc integerValue] == dpc ) {
// ...we have a match, edit properties
ins.dpc = #(dpc);
ins.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
ins.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
ins.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
}
// These are managed objects, so saving the context saves all the changes
NSError *saveError;
[context save:&saveError];
if ( saveError ) {
NSLog(#"Save error: %#", [error localizedDescription]);
}

Core Data: deleteObject crashes application, NSPredicate the cause?

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];

MR_save seems to delete objects in context

I am using magical record to handle core data. I have code that creates a Entity which looks as follows:
if ( gameEntity == nil )
{
gameEntity = [GameEntity MR_createEntity];
}
gameEntity.opponent = opponent;
gameEntity.me = me;
etc...
When I then query for game entities as follows:
NSPredicate* gamePredicate = [NSPredicate predicateWithFormat:#"SELF IN %# AND gameState != %#", me.myGames, GAME_ENTITY_STATE__OTHER];
NSFetchRequest* fetchRequest = [GameEntity MR_requestAllWithPredicate:gamePredicate];
NSArray * results = [[NSManagedObjectContext MR_defaultContext] executeFetchRequest:fetchRequest error:nil];
return results;
Results has a size of 1 (which makes sense).
However when I save the context as follows:
[[NSManagedObjectContext MR_defaultContext] MR_save];
And run the same fetchRequest above, all of a sudden the GameEntity is gone and there are no results in NSArray.
Even weirder when I shut down the app and run it again, it finds the GameEntity. So it was in fact saved. Why is MR_save essentially breaking my NSManagedObject context?
All the save and creates are being run on the main thread.
So it turns out that the problem was my predicate, which makes this more of a CoreData problem than a magical record.
I changed the predicate from this:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"SELF IN %# AND gameState != %#", me.myGames, GAME_ENTITY_STATE__OTHER];
To this:
predicate = [NSPredicate predicateWithFormat:#"me == %# AND gameState != %#", me, GAME_ENTITY_STATE__OTHER];
For some reason the first predicate is not always accurate. While the second one seems to always work.
While this does answer the question, I'd be curious why people believe why the first predicate doesn't always work...

Resources