3
votes

In prepping my app for an update, I've found a weird issue that's so far been a bit of a head-scratcher.

I have a simple method that fetches a managed object, updates one attribute and then saves the change to the persistent store. The weird thing is that some of the time, it doesn't seem to actually save all the way down the stack to the DB and yet it returns true for a successful save and doesn't populate the NSError object.

I've verified this by turning on SQL logging - in one call, I see no UPDATE statement but in a second call of the same method with the same inputs, I see the UPDATE.

Really weird. I must be doing something wrong but I've been staring at this all day and I can't figure it out.

Here's the method in question:

+ (void)markTemplateAsPurchasedWithProductID:(NSString *)productID inContext:(NSManagedObjectContext *)context {    
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
NSEntityDescription *entity = [NSEntityDescription entityForName:@"TripTemplate" inManagedObjectContext:context]; 
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"(productID = %@)", productID]]; 
[fetchRequest setEntity:entity]; 
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error]; 
[fetchRequest release];

if ([fetchedObjects count] > 0) {
    TripTemplate *template = [fetchedObjects lastObject];
    template.purchased = [NSNumber numberWithBool:YES];

    NSLog(@"Marking '%@' as Purchased: %@", template.name, template.purchased);

    NSError *saveError;        
    if (![context save:&saveError])
        NSLog(@"Error Saving Purchased For Template: %@ - %@", template, saveError);
} else {
   ...
   //log fetch error
}
}

Here are the two sets of logs I see when calling this method.

I've verified that in both cases they're being called from the main thread.

They're being run one after the other.

Run #1 (no SQL update):

2011-04-30 17:25:27.107 App[15024:707] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZLASTAPPSTOREPRICE, t0.ZPURCHASED, t0.ZISFREE, t0.ZNAME, t0.ZPRODUCTID, t0.ZSERVERID, t0.ZTRIPDESCRIPTION, t0.ZAUTHORDESCRIPTION, t0.ZAUTHORURL, t0.ZCREATEDAT, t0.ZAUTHORNAME FROM ZTRIPTEMPLATE t0 WHERE t0.ZPRODUCTID = ?

2011-04-30 17:25:27.110 App[15024:707] CoreData: annotation: sql connection fetch time: 0.0028s

2011-04-30 17:25:27.111 App[15024:707] CoreData: annotation: total fetch execution time: 0.0043s for 1 rows.

2011-04-30 17:25:27.112 App[15024:707] Marking 'Steve's Creations' as Purchased: 1

Run #2 (SQL update):

2011-04-30 17:27:37.536 App[15024:707] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZLASTAPPSTOREPRICE, t0.ZPURCHASED, t0.ZISFREE, t0.ZNAME, t0.ZPRODUCTID, t0.ZSERVERID, t0.ZTRIPDESCRIPTION, t0.ZAUTHORDESCRIPTION, t0.ZAUTHORURL, t0.ZCREATEDAT, t0.ZAUTHORNAME FROM ZTRIPTEMPLATE t0 WHERE t0.ZPRODUCTID = ?

2011-04-30 17:27:37.537 App[15024:707] CoreData: annotation: sql connection fetch time: 0.0015s

2011-04-30 17:27:37.538 App[15024:707] CoreData: annotation: total fetch execution time: 0.0024s for 1 rows.

2011-04-30 17:27:37.539 App[15024:707] Marking 'Steve's Creations' as Purchased: 1

2011-04-30 17:27:37.540 App[15024:707] CoreData: sql: BEGIN EXCLUSIVE

2011-04-30 17:27:37.542 App[15024:707] CoreData: sql: UPDATE ZTRIPTEMPLATE SET ZPURCHASED = ?, Z_OPT = ? WHERE Z_PK = ? AND Z_OPT = ?

2011-04-30 17:27:37.544 App[15024:707] CoreData: sql: COMMIT

Any suggestions where I might start looking for my problem?

In both cases, the fetch works so it seems the MOC is at least capable of talking to the store.

1
Are you relying on the SQL logging to tell you nothing has been changed or does the managed object itself not change? Does the change appear upon restart?TechZen
I just used the SQL logging to verify. If I log the purchased attribute right before and after the save, it shows as true but if I refresh the object after the save, the 'purchased' attribute goes back to false.Hunter
Do you have multiple context or multiple thread/operations?TechZen
In other parts of the app, with other MOs, yes. All of the interactions we're talking about here (and this type of MO in general) are only interacted with on the main thread. In general, I'm pretty careful about one MOC per thread so while it's not impossible I've screwed up, it's not my first Core Data threading rodeo either.Hunter
Okay, I ask because direct SQL is an uncertain tool to use with Core Data as the API isn't always doing what you assume it would be if it was just an SQL wrapper. Many of the optimizations seem to block reads and writes you might expect. However, if the object itself reverts then the changes are not being saved.TechZen

1 Answers

1
votes

In looking at the code, the only thing I could suggest as an error would be that you are somehow passing different context to the method. If so, and you don't merge the contexts, then changes won't show if you save in one but examine from another.

I would suggest logging the entire template object, the context and the context's updatedObjects. You need to make sure you are talking to the objects you think you are.