Произвольные атрибуты в базовых данных - PullRequest
2 голосов
/ 25 августа 2010

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

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

Entry <--->> ExtraAttribute

, где ExtraAttribute имеет свойства key и value, а key уникален для записи ExtraAttribute.

ХотяКод для решения этой проблемы немного сложен, это приемлемо.Настоящая проблема заключается в сортировке .

Мне нужно отсортировать записи, имеющие заданный атрибут ExtraAttribute по этому атрибуту.Используя хранилище SQL, очевидно, что для самих Core Data невозможно отсортировать записи по значению связанного ExtraAttribute с заданным ключом.(Расстраивает, поскольку это возможно с другими хранилищами, и тривиально в самом SQL.)

Единственный способ, который я могу найти, - это отсортировать записи самостоятельно, а затем записать атрибут displayOrder обратно в хранилище,и сортировать базовые данные по displayOrder.Я делаю это с помощью следующего метода класса Entry.(При этом используются некоторые методы и глобальные функции, которые не показаны, но, надеюсь, вы сможете получить суть. Если нет, спросите, и я уточню.)

NSInteger entryComparator(id entry1, id entry2, void *key) {
    NSString *v1 = [[entry1 valueForPropertyName:key] description];
    NSString *v2 = [[entry2 valueForPropertyName:key] description];
    return [v1 localizedCompare:v2];
}

@implementation Entry

...

// Unified builtin property and extraAttribute accessor;
// expects human-readable name (since that's all ExtraAttributes have).
- (id)valueForPropertyName:(NSString *)name {
    if([[Entry humanReadablePropertyNames] containsObject:name])  {
        return [self valueForKey:
            [Entry propertyKeyForHumanReadableName:name]];
    } else {
        NSPredicate *p = [NSPredicate predicateWithFormat:
            @"key = %@", name];
        return [[[self.extraAttributes filteredSetUsingPredicate:p]
                                            anyObject] value];
    }
}

+ (void)sortByPropertyName:(NSString *)name
        inManagedObjectContext:(NSManagedObjectContext *)moc {
    BOOL ascending = [Entry propertyIsNaturallyAscending:name];
    [Entry sortWithFunction:entryComparator
        context:name ascending:ascending moc:moc];
    [[NSUserDefaults standardUserDefaults]
        setObject:name
        forKey:@"entrySortPropertyName"];
}

// Private method.
+ (void)sortWithFunction:(NSInteger (*)(id, id, void *))sortFunction
        context:(void *)context
        ascending:(BOOL)ascending
        moc:(NSManagedObjectContext *)moc {

    NSEntityDescription *entityDescription = [NSEntityDescription
        entityForName:@"Entry" inManagedObjectContext:moc];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];
    NSError *error;
    NSArray *allEntries = [moc executeFetchRequest:request error:&error];
    [request release];
    if (allEntries == nil) {
        showFatalErrorAlert(error);
    }

    NSArray *sortedEntries = [allEntries
        sortedArrayUsingFunction:sortFunction context:context];

    int i, di;
    if(ascending) {
        i = 0; di = 1;
    } else {
        i = [sortedEntries count]; di = -1;
    }
    for(Entry *e in sortedEntries) {
        e.displayOrder = [NSNumber numberWithInteger:i];
        i += di;
    }
    saveMOC(moc);
}

@end

У этого есть две основные проблемы:

  1. Это медленно, даже с небольшими наборами данных.
  2. Это может занять произвольно большой объем памяти и, следовательно, сбой при больших наборах данных.

Я открыт длялюбые предложения, которые проще, чем вырвать Core Data и напрямую использовать SQL.Большое спасибо.

РЕДАКТИРОВАТЬ Спасибо за ваши ответы.Надеюсь, это прояснит вопрос.

Вот типичный набор данных: существует n объектов ввода, и у каждого есть отдельный набор пар ключ / значениесвязано с этим.Здесь я перечисляю пары ключ / значение под каждой записью:

Entry 1:
    Foo => Hello world
    Bar => Lorem ipsum

Entry 2:
    Bar => La dee da
    Baz => Goodbye cruel world

Здесь я хочу отсортировать записи по любому из ключей "Foo", "Bar" или "Baz".Если данная запись не имеет значения для ключа, она должна сортироваться как пустая строка.

Хранилище SQLite не может отсортировать по неизвестному ключу, используя -valueForUndefinedKey :;попытка сделать это приводит к NSInvalidArgumentException, причина keypath Foo not found in entity <NSSQLEntity Entry id=2>.

Как отмечено в документации, только фиксированный набор селекторов будет работать с дескрипторами сортировки, использующими хранилище SQL.

РЕДАКТИРОВАТЬ 2

Предположим, есть три экземпляра E1, E2 и E3 моей сущности, и пользователь присоединяет к каждому из этих экземпляров настраиваемые свойства «Имя» и «Год».Тогда у нас может быть:

E1    Bob      2010
E2    Alice    2009
E3    Charles  2007

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

E2    Alice    2009
E1    Bob      2010
E3    Charles  2007

или по дате:

E3    Charles  2007
E2    Alice    2009
E1    Bob      2010

и т. Д.

1 Ответ

3 голосов
/ 25 августа 2010

Первый вопрос: зачем вам хранить сортировку в базе данных? Если вы всегда сортируете в свойстве ключа, просто используйте дескриптор сортировки всякий раз, когда вам нужно получить к ним доступ в отсортированном порядке.

Второй вопрос, почему вы пишете свою собственную процедуру сортировки?

Этот дизайн кажется довольно сложным. Я понимаю необходимость произвольного хранения пар ключ-значение, я разработал аналогичную систему в своей книге. Однако мне неясно, нужно ли сортировать эти значения, а также нужна ли пользовательская процедура сортировки, такая как эта.

Если бы вы могли объяснить необходимость сортировки, я мог бы предложить лучшую стратегию.

Кроме того, я бы весьма рекомендовал бы рассмотреть два метода -valueForUndefinedKey: и -setValue: forUndefinedKey: как более чистое решение вашей проблемы. Это позволит вам написать код вроде:

[myObject valueForKey:@"anythingInTheWorld"];
[myObject setValue:someValue forKey:@"anythingInTheWorld"];

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

Обновление

Дизайн -valueForUndefinedKey: предназначен только для использования в коде, он не предназначен для доступа к магазину. Мне все еще немного неясно с вашими целями.

Учитывая следующую модель:

Entity <-->> Property

В этом дизайне Property имеет два атрибута:

Key
Value

Отсюда вы можете получить доступ к любому свойству на Entity через -valueForUndefinedKey:, потому что под прикрытием Entity выйдет и принесет связанный Property для этого ключа. Таким образом вы получаете динамические значения на вашем Entity.

Теперь вопрос сортировки. С этим дизайном вы можете сортировать напрямую по SQLite, потому что вы действительно сортируете по сущности Property. Хотя мне все еще неясно, какова конечная цель сортировки. Какое значение это имеет? Как это будет использоваться?

Обновление: дизайн пересмотрен

Последний предложенный мною проект был неверным. При более глубоком размышлении это проще, чем я предлагал. Ваша цель может быть достигнута с оригинальным дизайном Entity <- >> Property. Однако в методе -setValue: forKey: предстоит проделать еще немного работы. Логика следующая:

  1. Внешний код, называемый -setValue: forKey: на Entity.
  2. Метод -setValue: forKey: пытается получить Property.
  3. Если существует Property, то значение обновляется.
  4. Если Property не существует, то для каждого Entity создается Property с установленным значением по умолчанию (предполагается, что это пустая строка).

Единственный удар по производительности - это когда вводится новый ключ. Кроме этого он должен работать без каких-либо штрафов за производительность.

...