3

I am trying to run a batch update on selected objects in my Core Data database (SQLite) but my request returns 0 items updated (no error message). My entity "SDRDFileObject" has a property selected (Bool in my model), which I want to set to NO for all objects satisfying my fetch predicate:

 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(isLeaf == 1) AND (direction ==  1) AND (myLR == 1)"];

 NSEntityDescription *entity = [NSEntityDescription entityForName:@"SDRDFileObject"
                                     inManagedObjectContext:self.context];

NSBatchUpdateRequest *reqL = [[NSBatchUpdateRequest alloc] initWithEntity:entity];
reqL.predicate = predicate;
reqL.resultType = NSUpdatedObjectIDsResultType;

reqL.includesSubentities = YES;
reqL.propertiesToUpdate = @{
                            @"selected" : @(NO)
                            };

NSError *error;
NSBatchUpdateResult *resL = (NSBatchUpdateResult *)[self.context executeRequest:reqL error:&error];

This returns 0 items updated. However, if I use the same settings to make an estimate of the number of affected objects I get 1764 (which is correct):

 NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setPredicate:predicate];
[request setResultType:NSUpdatedObjectIDsResultType];
[request setReturnsDistinctResults:YES];
[request setPropertiesToFetch:@[@"selected"]];


NSError *err;
NSUInteger count = [self.context countForFetchRequest:request error:&err];

NSLog(@"Estimated fetch request count : %li",count);

Does anyone have suggestions as to what I am doing wrong here? Should I write the keyPath any different? I appreciate suggestions and help.

Update I followed the suggestion for debugging the SQL commands and it seems to fail, although I am not sure how to interpret the error message. The first SQL calls are for the countForFetchRequest which suceeds and then comes the batch request that fails.

CoreData: sql: SELECT COUNT( DISTINCT t0.Z_PK) FROM ZSDRDFILEOBJECT t0     WHERE ( t0.ZISLEAF = ? AND  t0.ZDIRECTION = ? AND  t0.ZMYLR = ?) 
CoreData: annotation: total count request execution time: 0.0005s for count of 0.
2017-11-15 16:56:09.560121+0100 TEST[69278:3739361] Estimated fetch  request count : 1764
CoreData: sql: BEGIN EXCLUSIVE
CoreData: sql: SELECT 0, t0.Z_PK FROM ZSDRDFILEOBJECT t0 WHERE (  t0.ZISLEAF = ? AND  t0.ZDIRECTION = ? AND  t0.ZMYLR = ?) 
CoreData: annotation: sql connection fetch time: 0.0000s
CoreData: annotation: total fetch execution time: 0.0001s for 0 rows.
CoreData: sql: UPDATE OR FAIL ZSDRDFILEOBJECT SET ZSELECTED = ?, Z_OPT = (Z_OPT + 1) WHERE (ZISLEAF = ? AND ZDIRECTION = ? AND ZMYLR = ?) 
CoreData: sql: pragma auto_vacuum
CoreData: annotation: sql execution time: 0.0000s
CoreData: sql: pragma auto_vacuum=2
CoreData: annotation: sql execution time: 0.0003s
CoreData: sql: COMMIT
Trond Kristiansen
  • 2,379
  • 23
  • 48
  • Perhaps all the values have already 'selected = NO', so no values are modified. At first sigh, your code seems OK – webo80 Nov 15 '17 at 15:07
  • I double-checked but that is not the issue – Trond Kristiansen Nov 15 '17 at 15:21
  • Perhaps you can use [this](https://stackoverflow.com/questions/6428630/xcode4-and-core-data-how-to-enable-sql-debugging) to enable sql debugging, to get more verbosity and help you understand the queries. IF you can't figure out what's happening, paste the resulting log here – webo80 Nov 15 '17 at 15:25
  • I added the output from running with debug as you suggested – Trond Kristiansen Nov 15 '17 at 16:02
  • 3
    The first SQL count returns 0, but the context returns 1764. So it looks like the objects which meet your criteria have not yet been saved to the SQLite store. The batch update works directly on the SQLite store, so it cannot update the unsaved items. – pbasdf Nov 15 '17 at 16:18
  • 1
    @pbasdf exactly! Please, post it as an answer, so the OP can mark it – webo80 Nov 15 '17 at 16:45
  • @pbasdf Please post your comment as the answer. That worked ! – Trond Kristiansen Nov 15 '17 at 16:59

1 Answers1

4

The first SQL count returns 0, but the context returns 1764. So it looks like the objects which meet your criteria have not yet been saved to the SQLite store. The batch update works directly on the SQLite store, so it cannot update the unsaved items.

So either save the context first, or update the objects directly in the context - which should be quick, since they are already registered with the context and do not need to be fetched.

pbasdf
  • 21,386
  • 4
  • 43
  • 75
  • Thanks for your quick help! – Trond Kristiansen Nov 15 '17 at 17:38
  • Could you give me a hint as to how I should "update the objects directly in the context". I would prefer not to save the context, but just work in memory if that is possible. – Trond Kristiansen Nov 16 '17 at 07:46
  • Just execute a fetch on the context to get an array of all the objects, then loop through the array setting the `selected` attribute to NO. Or you could even use `setValue:forKey:` directly on the array, to avoid the need to loop. – pbasdf Nov 16 '17 at 09:00
  • I am a little confused here. If I have to loop through all the items after issuing the `NSBatchUpdateRequest` that I thought would do the job, what did I gain from doing the batch update? I am still having some issues getting this to work as you probably understand :) – Trond Kristiansen Nov 16 '17 at 19:10
  • I could also add that I do loop over the objects after the batch update trying to refresh context, but still no effect on my table view showing the updated values: `NSManagedObject *managedObject =[self.context objectWithID:objectID]; [self.context refreshObject:managedObject mergeChanges:false];` – Trond Kristiansen Nov 16 '17 at 19:17
  • The NSBatchUpdateRequest is of no use to you. It is intended for making updates to a large number of objects in the store, without having to load them into memory. Since your objects are all in memory (and not in the store), there is no benefit to using NSBatchUpdateRequest. So my suggestion of looping through all the objects was an alternative to using batch update, not an additional step. The refreshObject:mergeChanges: will also have no effect, since there is no entry in the store to get the refreshed values from. – pbasdf Nov 16 '17 at 20:12
  • Ah That makes sense. Thanks – Trond Kristiansen Nov 16 '17 at 20:59