Core Data fetch predicate nil check failing/unexpected results?

861 views Asked by At

I have a Core Data layer with several thousand entities, constantly syncing to a server. The sync process uses fetch requests to check for deleted_at for the purposes of soft-deletion. There is a single context performing save operations in a performBlockAndWait call. The relationship mapping is handled by the RestKit library.

The CoreDataEntity class is a subclass of NSManagedObject, and it is also the superclass for all our different core data object classes. It has some attributes that are inherited by all our entities, such as deleted_at, entity_id, and all the boilerplate fetch and sync methods.

My issue is some fetch requests seem to return inconsistent results after modifications to the objects. For example after deleting an object (setting deleted_at to the current date):

[CoreDataEntity fetchEntitiesWithPredicate:[NSPredicate predicateWithFormat:@"deleted_at==nil"]];

Returns results with deleted_at == [NSDate today]

I have successfully worked around this behavior by additionally looping through the results and removing the entities with deleted_at set, however I cannot fix the converse issue:

[CoreDataEntity fetchEntitiesWithPredicate:[NSPredicate predicateWithFormat:@"deleted_at!=nil"]];

Is returning an empty array in the same conditions, preventing a server sync from succeeding.

I have confirmed deleted_at is set on the object, and the context save was successful. I just don't understand where to reset whatever cache is causing the outdated results?

Thanks for any help!

Edit: Adding a little more information, it appears that once one of these objects becomes corrupted, the only way get it to register is modifying the value again. Could this be some sort of Core Data index not updating when a value is modified?

Update: It appears to be a problem with RestKit https://github.com/RestKit/RestKit/issues/2218

3

There are 3 answers

4
theMikeSwan On

Try adding an extra attribute deleted that is a bool with a default of false. Then the attribute is always set and you can look for entities that are either true or false depending on your needs at the moment. If the value is true then you can look at deleted_at to find out when.

Alternatively try setting the deleted_at attribute to some old date (like perhaps 1 Jan 1980), then anything that isn't deleted will have a fixed date that is too old to have been set by the user.

Edit: There is likely some issue with deleted_at having never been touched on some entities that is confusing the system. It is also possible that you have set the fetch request to return results in the dictionary style in which case recent changes will not be reflected in the fetch results.

3
Marcus S. Zarra On

First, after a save have you looked in the store to make sure your changes are there? Without seeing your entire Core Data stack it is difficult to get a solid understanding what might be going wrong. If you are saving and you see the changes in the store then the question comes into your contexts. How are they built and when. If you are dealing with sibling contexts that could be causing your issue.

More detail is required as to how your core data stack looks.

Yes, the changes are there. As I mentioned in the question, I can loop through my results and remove all those with deleted_at set successfully

That wasn't my question. There is a difference between looking at objects in memory and looking at them in the SQLite file on disk. The questions I have about this behavior are:

  1. Are the changes being persisted to disk before you query for them again
  2. Are you working with multiple contexts and potentially trying to fetch from a stale sibling.

Thus my questions about on disk changes and what your core data stack looks like.

Threading

If you are using one context, are you using more than one thread in your app? If so, are you using that context on more than one thread?

I can see a situation where if you are violating the thread confinement rules you can be corrupting data like this.

0
bteapot On

You are apparently using some sintactic sugar extension to Core Data. I suppose that in your case it is a SheepData, right?

fetchEntitiesWithPredicate: there implemented as follows:

+ (NSArray*)fetchEntitiesWithPredicate:(NSPredicate*)aPredicate
{
    return [self fetchEntitiesWithPredicate:aPredicate inContext:[SheepDataManager sharedInstance].managedObjectContext];
}

Are you sure that [SheepDataManager sharedInstance].managedObjectContext receives all the changes that you are making to your objects? Is it receives notifications of saves, or is it child context of your save context?

Try to replace your fetch one-liner with this:

[<your saving context> performBlockAndWait:^{
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"CoreDataEntity"];
    request.predicate = [NSPredicate predicateWithFormat:@"deleted_at==nil"];

    NSArray *results = [<your saving context> executeFetchRequest:request error:NULL];
}];