Запрос на выборку возвращает старые данные

Я получаю устаревшие результаты при выполнении запроса на выборку NSManagedObjectContextObjectsDidChangeNotification.

В качестве примера рассмотрим каталоги с документами, которые можно удалить логически с помощью логического атрибута (softDeleted). Запрос на выборку должен возвращать все каталоги, в которых есть хотя бы один неудаленный документ (directoriesWithDocuments).

Исходное состояние — один каталог с одним удаленным документом. directoriesWithDocuments возвращает пустой массив.

Следующий код восстанавливает документ, присваивая логическому значению softDeleted значение NO.

[_context.undoManager beginUndoGrouping];
[_context.undoManager setActionName:@"undelete"];
document.softDeleted = @(NO);
NSError *error;
BOOL success = [_context save:&error]; // This triggers the notification
[_context.undoManager endUndoGrouping];

Сохранение вызывает NSManagedObjectContextObjectsDidChangeNotification. Я ожидал, что directoriesWithDocuments вернет каталог, но вместо этого он по-прежнему возвращает пустой массив.

- (void)objectsDidChangeNotification:(NSNotification*)notification
{
    NSArray *objects = [self directoriesWithDocuments]; // Still empty!
}

Тем не менее, если я выполню directoriesWithDocuments после сохранения контекста либо сразу, либо в следующем цикле выполнения, он вернет каталог, как и ожидалось.

Это код запроса на выборку:

- (NSArray*)directoriesWithDocuments
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY documents.softDeleted == NO"];
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Directory"];
    fetchRequest.predicate = predicate;
    fetchRequest.includesPendingChanges = YES; // Just in case
    NSError *error = nil;
    NSArray *objects = [_context executeFetchRequest:fetchRequest error:&error];
    return directories;
}

Я подозреваю, что в контексте есть какой-то кеш для запросов на выборку, который не очищается до тех пор, пока не будет обработано уведомление. Ожидается ли, что Core Data будет вести себя именно так? Или я что-то не так делаю?

Обходной путь

В настоящее время я задерживаю выполнение запроса на выборку следующим образом:

- (void)objectsDidChangeNotification:(NSNotification*)notification
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // HACK: Give time to Core Data to process pending changes or invalidate caches
        NSArray *objects = [self directoriesWithDocuments]; // Returns the directory as expected
    }];

}

Эксперименты

По предложению @MikePollard я проверил возвращаемое значение directoriesWithDocuments в NSManagedObjectContextWillSaveNotification и NSManagedObjectContextDidSaveNotification. Результаты (по порядку):

  1. NSManagedObjectContextObjectsDidChangeNotification: пусто (неверно)
  2. NSManagedObjectContextWillSaveNotification: пусто (неверно)
  3. NSManagedObjectContextDidSaveNotification: 1 каталог (правильно)

person hpique    schedule 06.03.2014    source источник
comment
Вы уверены, что выполняете всю работу в одном и том же NSManagedObjectContext?   -  person Marius Waldal    schedule 06.03.2014
comment
Мне было бы интересно услышать, какие результаты вы получите, если вместо этого вызовете directoriesWithDocuments в результате NSManagedObjectContextWillSaveNotification, а также уведомления NSManagedObjectContextDidSaveNotification?   -  person Mike Pollard    schedule 06.03.2014
comment
@MariusFalkenbergWaldal Да.   -  person hpique    schedule 06.03.2014
comment
Меня поражает, что предикат будет переведен в оператор SQL, поэтому, пока сохранение не попадет в БД, вы не получите желаемого результата... (Не то чтобы вы представляли это, просто читая документацию NSFetchRequest.)   -  person Mike Pollard    schedule 06.03.2014
comment
@MikePollard Отредактировал вопрос, чтобы включить результаты.   -  person hpique    schedule 06.03.2014
comment
"1 каталог" правильный ответ?   -  person Mike Pollard    schedule 06.03.2014
comment
Тогда я подозреваю, что это именно тот случай. то есть выборка выполняется непосредственно в базе данных SQL и не принимает во внимание измененное состояние контекста. Бьюсь об заклад, если вы включите -com.apple.CoreData.SQLDebug 1, вы увидите оператор SQL.   -  person Mike Pollard    schedule 06.03.2014
comment
@MikePollard Вы правы. Возможно, вам следует опубликовать это как ответ. Любые идеи о том, как обойти это? Я боюсь, что, поскольку невозможно узнать, будет ли выполняться запрос на выборку к базе данных SQL или нет, синхронно выполнять любой запрос в NSManagedObjectContextObjectsDidChangeNotification небезопасно.   -  person hpique    schedule 06.03.2014
comment
давайте продолжим это обсуждение в чате   -  person Mike Pollard    schedule 06.03.2014
comment
Еще одно предостережение, на которое стоит обратить внимание: если вы измените тип результата запроса на выборку на словарь, он автоматически отключит includePendingChanges — stackoverflow.com/questions/15893794/   -  person Anurag    schedule 06.03.2014


Ответы (2)


Меня поражает, что предикат будет переведен в оператор SQL, поэтому, пока сохранение не попадет в БД, вы не получите желаемого результата... (Не то чтобы вы представляли это, просто читая NSFetchRequest документация.)

Поэтому попробуйте выполнить выборку в результате ошибки NSManagedObjectContextDidSaveNotification.

Бьюсь об заклад, если вы включите -com.apple.CoreData.SQLDebug 1, вы увидите инструкцию SQL.

person Mike Pollard    schedule 06.03.2014

Прежде всего, похоже, что у вас есть логический атрибут под названием «удалено», определенный в вашей модели.

Я помню, что это может быть серьезной проблемой, потому что это конфликтует (на уровне KVC) с NSManagedObject isDeleted. Возможно, вы захотите изменить это, чтобы убедиться, что это не виновник.

Редактировать

Спасибо за ответ. Я использовал удаленный в качестве простого примера; это не тот атрибут, который я использую. Изменю его на softDeleted, чтобы избежать путаницы

Я обновил свое предложение ниже, чтобы оно соответствовало softDeleted в вашем примере.


Тем не менее, я думаю, что здесь все сводится к вопросу о том, что представляет собой «изменение» для includesPendingChanges = YES.

Прежде чем контекст завершит свое сохранение, единственное «изменение» касается объектов документа, а не объектов каталога.

Таким образом, когда запрос на выборку включает ожидающие изменения, нет объектов Каталога с ожидающими изменениями, поэтому вы получаете предыдущие результаты.

Чтобы проверить эту теорию, попробуйте следующее:

[_context.undoManager beginUndoGrouping];
[_context.undoManager setActionName:@"delete"];
document.softDeleted = @(NO);
[document.directory willChangeValueForKey:@"documents"] // any attribute will do really
[document.directory didChangeValueForKey:@"documents"]
NSError *error;
BOOL success = [_context save:&error];
[_context.undoManager endUndoGrouping];

То, что вы делаете с фальшивым изменением changeValueForKey, заключается в том, чтобы «загрязнить» связанный с ним объект каталога. Я предполагаю, что тогда он будет считаться «измененным» и, как таковой, будет включать результаты выборки.

person Trevor Squires    schedule 06.03.2014
comment
Спасибо за ответ. Я использовал deleted в качестве простого примера; это не тот атрибут, который я использую. Изменю его на softDeleted, чтобы избежать путаницы. - person hpique; 06.03.2014