Realm Cocoa: поиск нескольких объектов с помощью ПК

Давний наблюдатель, спрашивающий впервые.

Я использую Realm Cocoa (от Realm.io) в проекте и изо всех сил пытаюсь выполнить поиск по ПК. Допустим, у меня есть объект с именем RLMFoo, у которого есть первичный ключ с именем bar. У меня также есть список ПК, скажем, сохраненный в массиве:

NSArray *primaryKeys = @[@"bar1", @"bar2", @"bar3"]

Есть ли способ получить все объекты класса RLMFoo из моей области одним запросом?

Я пробовал до сих пор:

  1. Предикат в формате: [RLMFoo objectsInRealm:realm withPredicate:[NSPredicate predicateWithFormat:@"bar IN %@", primaryKeys]];
  2. Царство где: [RLMFoo objectsInRealm:realm where:@"bar IN %@", strippedIds];
  3. Предикат с блоком:

.

NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id
evaluatedObject, NSDictionary *bindings) {
                    RLMFoo *foo = (RLMFoo *)evaluatedObject;
                    return [primaryKeys containsObject:foo.bar];
                }];
        [RLMFoo objectsInRealm:realm withPredicate:predicate];

Но единственное, что я нашел работающим до сих пор, это запрашивать область для каждого значения первичного ключа и агрегировать результаты, что кажется медленным:

NSMutableArray *results = [NSMutableArray new];
for (NSString *primaryKey in primaryKeys) {
    RLMFoo *obj = [RLMFoo objectInRealm:realm forPrimaryKey:primaryKey];
    if (obj) {
        [results addObject:obj];
    }
}

Кто-нибудь знает лучший способ сделать это?

========= Объяснение того, почему первые три метода не работают =======

1) Причина, по которой это не работает, кажется очевидной из сообщения об исключении: IN принимает только 2 значения, хотя SQL-версия IN должна принимать столько, сколько необходимо. Как видно из исходного кода RLMQueryUtil.mm, то же самое относится и к оператору BETWEEN:

        if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) {
        // Inserting an array via %@ gives NSConstantValueExpressionType, but
        // including it directly gives NSAggregateExpressionType
        if (exp1Type != NSKeyPathExpressionType || (exp2Type != NSAggregateExpressionType && exp2Type != NSConstantValueExpressionType)) {
            @throw RLMPredicateException(@"Invalid predicate",
                                         @"Predicate with %s operator must compare a KeyPath with an aggregate with two values",
                                         compp.predicateOperatorType == NSBetweenPredicateOperatorType ? "BETWEEN" : "IN");
        }

Вот трассировка стека:

*** Terminating app due to uncaught exception 'Invalid predicate', reason: 'Predicate with IN operator must compare a KeyPath with an aggregate with two values'
*** First throw call stack:
(
    0   CoreFoundation      0x03334946 __exceptionPreprocess + 182
    1   libobjc.A.dylib     0x0292aa97 objc_exception_throw + 44
    2   <redacted>          0x00190569 _ZN12_GLOBAL__N_127update_query_with_predicateEP11NSPredicateP9RLMSchemaP15RLMObjectSchemaRN7tightdb5QueryE + 2553
    3   <redacted>          0x0018f7ea _Z27RLMUpdateQueryWithPredicatePN7tightdb5QueryEP11NSPredicateP9RLMSchemaP15RLMObjectSchema + 378
    4   <redacted>          0x0018b23c _Z13RLMGetObjectsP8RLMRealmP8NSStringP11NSPredicate + 748
    5   <redacted>          0x0017d721 +[RLMObject objectsInRealm:withPredicate:] + 161

2) Это очень похоже по причине и трассировке стека:

*** Terminating app due to uncaught exception 'Invalid predicate', reason: 'Predicate with IN operator must compare a KeyPath with an aggregate with two values'
*** First throw call stack:
(
    0   CoreFoundation      0x03328946 __exceptionPreprocess + 182
    1   libobjc.A.dylib     0x0291ea97 objc_exception_throw + 44
    2   <redacted>          0x00184599 _ZN12_GLOBAL__N_127update_query_with_predicateEP11NSPredicateP9RLMSchemaP15RLMObjectSchemaRN7tightdb5QueryE + 2553
    3   <redacted>          0x0018381a _Z27RLMUpdateQueryWithPredicatePN7tightdb5QueryEP11NSPredicateP9RLMSchemaP15RLMObjectSchema + 378
    4   <redacted>          0x0017f26c _Z13RLMGetObjectsP8RLMRealmP8NSStringP11NSPredicate + 748
    5   <redacted>          0x00171751 +[RLMObject objectsInRealm:withPredicate:] + 161
    6   <redacted>          0x00171465 +[RLMObject objectsInRealm:where:args:] + 213
    7   <redacted>          0x001712f3 +[RLMObject objectsInRealm:where:] + 419

3) Этот, опять же, очень похож. К чему это сводится, так это к отсутствию поддержки со стороны области для полного набора функций NSPredicate.

*** Terminating app due to uncaught exception 'Invalid predicate', reason: 'Only support compound and comparison predicates'
*** First throw call stack:
(
    0   CoreFoundation      0x03308946 __exceptionPreprocess + 182
    1   libobjc.A.dylib     0x028fea97 objc_exception_throw + 44
    2   <redacted>          0x001638bd _ZN12_GLOBAL__N_127update_query_with_predicateEP11NSPredicateP9RLMSchemaP15RLMObjectSchemaRN7tightdb5QueryE + 4397
    3   <redacted>          0x0016240a _Z27RLMUpdateQueryWithPredicatePN7tightdb5QueryEP11NSPredicateP9RLMSchemaP15RLMObjectSchema + 378
    4   <redacted>          0x0015de5c _Z13RLMGetObjectsP8RLMRealmP8NSStringP11NSPredicate + 748
    5   <redacted>          0x00150341 +[RLMObject objectsInRealm:withPredicate:] + 161

person user3099609    schedule 06.01.2015    source источник


Ответы (2)


ваши первые два варианта должны работать без проблем. Самый простой способ написать этот запрос:

NSArray *primaryKeys = @[@"bar1", @"bar2", @"bar3"]
RLMResults *results = [RLMFoo objectsWhere:@"id IN %@", primaryKeys];

Метод под названием objectsWithPredicate тоже работает. Можете ли вы поделиться своим кодом/трассировкой стека при использовании первых нескольких методов? Я думаю, что есть проблемы, происходящие в другом месте

person yoshyosh    schedule 07.01.2015
comment
Здравствуйте и спасибо за ответ. Я обновил исходный вопрос. Вкратце: IN используется Realm как псевдоним для BETWEEN, поэтому может принимать только 2 значения. В то время как предикат блока совершенно не поддерживается. - person user3099609; 07.01.2015
comment
Эй, нет проблем! Можете ли вы обновить начальные значения и объекты, на которых вы фактически тестируете эти методы? Примеры foo bar работают с оператором IN. Я пытаюсь воспроизвести ошибку, которую вы видите - person yoshyosh; 07.01.2015
comment
должен быть более быстрый и удобный способ сделать это - person Peter Lapisu; 15.09.2015
comment
Спасибо, Йошиош. - person user3182143; 24.05.2016

С вашей помощью я смог найти причину такого поведения. Мой реальный код был ближе к:

  NSArray *primaryKeys = @[ @"bar1", @"bar2", @"bar3" ];

  NSString *primaryKeyName = @"bar";
  RLMResults *results =
      [RLMFoo objectsInRealm:self.realm
               withPredicate:[NSPredicate predicateWithFormat:@"%@ IN %@",
                                                              primaryKeyName,
                                                              primaryKeys]];

Который был в методе, а ПК и имя ПК были параметрами вызова метода. Теоретически это позволило бы моему классу извлекать любые объекты с любым именем PK. Оказывается, моя ошибка заключается в предположении, что predicateWithFormat в этом случае будет работать аналогично stringWithFormat и правильно заменит имя PK в качестве первого аргумента и примет массив PK в качестве второго. Однако решение оказалось сначала сборкой строки формата, а затем предиката:

  RLMResults *results = [RLMFoo
      objectsInRealm:self.realm
       withPredicate:[NSPredicate
                         predicateWithFormat:
                             [NSString stringWithFormat:@"%@ IN %%@", primaryKeyName],
                             primaryKeys]];

Спасибо Йошиош за ваше время.

person user3099609    schedule 08.01.2015
comment
Отлично, приятно слышать, что вы нашли решение. Вы также можете использовать первый пример, если замените @%@ IN %@ на @%K IN %@. %K сообщает предикату с форматом, что вы хотите поместить строку как свойство, а не чистое значение - person yoshyosh; 08.01.2015
comment
Это еще лучшее решение. - person user3099609; 09.01.2015
comment
Большое спасибо, user3099609. Сначала я не понимал ваш ответ в течение последних 3 дней, особенно primaryKeyName. Я не знал, что это такое, и я ввел текст ввода в качестве имени первичного ключа. Так что это было в конечном счете неправильно, а также в primaryKeys, которые у меня были объекты вместо ввода ввели текст. Согласно моему проекту, primaryKeyName - это @name, а peimaryKeys - это NSArray *primaryKeys = @[textField.text]; Только сегодня я решил это с помощью Senior. - person user3182143; 24.05.2016
comment
Я сохранил такие данные, как name:@user3182143,empid:@001 и bloodGp:@AB+. Здесь моим primaryKeyName являются name, empid и bloodGP. Я должен передать это как primaryKeyName, а не textField, textView, текст метки или другие данные. В primaryKeys я должен передать textField, textView, текст метки или другие данные. - person user3182143; 24.05.2016