核心数据中的任意属性 [英] Arbitrary Attributes in Core Data

查看:145
本文介绍了核心数据中的任意属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简而言之,我想在iPad应用程序上将任意键/值对与Core Data实体的对象关联。



我当前的解决方案是与表示单对的另一实体的对多关系。在我的申请中,我有:

 条目< ---> ExtraAttribute 

其中 ExtraAttribute $ c>键和 value ,并且对ExtraAttribute的条目是唯一的。



虽然处理这个问题的代码稍微复杂,但可以接受。真正的问题是排序



我需要对那些具有给定ExtraAttribute的Entries进行排序。使用SQL存储,Core Data本身显然不可能通过给定的键对关联的ExtraAttribute的值对Entries进行排序。 (令人沮丧,因为这是可能与其他商店,并在SQL本身琐碎。)



我可以找到的唯一的技术是自己排序条目,然后写一个 displayOrder 属性返回到商店,并通过 displayOrder 排序Core Data。我在 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条目

...

//统一内置属性和extraAttribute访问器;
//期望人类可读的名字(因为这是所有的ExtraAttributes有)。
- (id)valueForPropertyName:(NSString *)name {
if [[entry humanReadablePropertyNames] containsObject:name]){
return [self valueForKey:
[Entry propertyKeyForHumanReadableName:名称]];
} 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];
}

//私有方法。
+(void)sortWithFunction:(NSInteger(*)(id,id,void *))sortFunction
context:(void *)context
升序:(BOOL) moc:(NSManagedObjectContext *)moc {

NSEntityDescription * entityDescription = [NSEntityDescription
entityForName:@EntryinManagedObjectContext: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;
}
(在sortedEntries中的Entry * e){
e.displayOrder = [NSNumber numberWithInteger:i];
i + = di;
}
saveMOC(moc);
}

@end

这有两个主要问题:


  1. 即使数据集很小,也很慢。



  2. 我可以使用任何比撕开Core Data和使用SQL更容易的建议。直。非常感谢。



    编辑感谢您的回答。希望这将澄清问题。



    这是一个典型的数据集:有 n个 Entry对象,每个都有一个不同键/值对的集合。这里我列出每个条目下的键/值对:

     条目1:
    Foo => Hello world
    Bar => Lorem ipsum

    条目2:
    Bar => La dee da
    Baz =>再见残酷的世界

    这里我想用任何键Foo 或Baz。



    SQLite存储不能使用-valueForUndefinedKey按未知密钥进行排序: ;尝试这样做导致 NSInvalidArgumentException ,原因 keypath Foo在实体中找不到< NSSQLEntity条目id = 2>



    如文档中所述,只有一组固定的选择器将使用SQL存储库处理排序描述符。



    EDIT 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


    $ b第一个问题是,为什么需要将排序存储在数据库中?为什么需要将排序存储在数据库中?如果你正在key属性排序,只要使用排序描述符,当你需要按排序顺序访问它们。



    第二个问题,为什么要编写自己的排序程序?



    这个设计看起来比较复杂。我理解了对键值对的仲裁存储的需要,我在我的书中设计了一个类似的系统。但是我不清楚需要排序这些值,也不需要一个自定义排序例程,如这一个。



    如果你能解释排序的需要

    另外,我会高度推荐查看两个方法 -valueForUndefinedKey:

    / code>和 -setValue:forUndefinedKey:作为更清洁的解决方案。这将允许你编写代码:

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

    并遵循正确的键值编码规则。



    更新



    -valueForUndefinedKey:设计仅用于代码,商店。



    给定以下模型:

     <$> c $ c> Entity<  - >>属性

    在此设计中,属性属性:

     

    从这里你可以通过 -valueForUndefinedKey:访问 Entity 因为在该覆盖下, Entity 将会出去并获取该键的相关 Property 。因此,你在 Entity 上获得动态值。



    现在排序的问题。有了这个设计,你可以直接在SQLite上排序,因为你真的排序在 Property 实体。虽然我仍然不清楚的排序的最终目标。它有什么价值?如何使用?



    更新:重新设计的设计



    我建议的最后一个设计是错误的。在更深的反思,它比我提出的更简单。您的目标可以用原始的实体< - >> 属性设计完成。但是,在 -setValue:forKey:方法中还有一些工作要做。逻辑如下:


    1. 外部代码 -setValue:forKey: Entity

    2. -setValue:forKey: Property

    3. 如果 Property 存在,则更新该值。

    4. 如果属性不存在,则会为每个属性创建属性 实体使用默认值设置(假定为空字符串)。

    唯一的性能命中是引入新密钥。除此之外,它应该工作,没有任何性能惩罚。


    In short, I want to associate arbitrary key/value pairs with the objects of a Core Data entity, on an iPad app.

    My current solution is to have a to-many relationship with another entity that represents a single pair. In my application, I have:

    Entry <--->> ExtraAttribute
    

    where ExtraAttribute has properties key and value, and the key is unique to the ExtraAttribute's Entry.

    Although the code to deal with this is slightly complicated, it is acceptable. The real problem comes with sorting.

    I need to sort those Entries that have a given ExtraAttribute by that attribute. Using the SQL store, it is apparently impossible for Core Data itself to sort the Entries by the value of the associated ExtraAttribute with a given key. (Frustrating, since this is possible with the other stores, and trivial in SQL itself.)

    The only technique I can find is to sort the entries myself, then write a displayOrder attribute back to the store, and have Core Data sort by the displayOrder. I do that with the following class method on Entry. (This uses a some methods and global functions not shown, but hopefully you can get the gist. If not, ask and I will clarify.)

    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
    

    This has two major problems:

    1. It's slow, even with small data sets.
    2. It can take an arbitrarily large amount of memory and hence crash with large data sets.

    I'm open to any suggestions that are easier than ripping out Core Data and using SQL directly. Thanks so much.

    EDIT Thank you for your answers. Hopefully this will clarify the question.

    Here is a typical data set: There are n Entry objects, and each one has a distinct set of key/value pairs associated with it. Here I am listing the key/value pairs under each entry:

    Entry 1:
        Foo => Hello world
        Bar => Lorem ipsum
    
    Entry 2:
        Bar => La dee da
        Baz => Goodbye cruel world
    

    Here I want to sort the entries by any of the keys "Foo", "Bar", or "Baz". If a given entry doesn't have a value for the key, it should sort like an empty string.

    The SQLite store cannot sort by an unknown key using -valueForUndefinedKey:; attempting to do so results in an NSInvalidArgumentException, reason keypath Foo not found in entity <NSSQLEntity Entry id=2>.

    As noted in the documentation, only a fixed set of selectors will work with sort descriptors using the SQL store.

    EDIT 2

    Suppose there are three instances E1, E2, and E3 of my entity, and the user attaches the custom properties 'Name' and 'Year' to each of these instances. Then we might have:

    E1    Bob      2010
    E2    Alice    2009
    E3    Charles  2007
    

    But we wish to present these instances to the user, sorted by any of these custom properties. For example, the user might sort by Name:

    E2    Alice    2009
    E1    Bob      2010
    E3    Charles  2007
    

    or by Date:

    E3    Charles  2007
    E2    Alice    2009
    E1    Bob      2010
    

    and so on.

    解决方案

    First question is, why do you need to store the sort in the database? If you are alway sorting in the key property, just use a sort descriptor whenever you need to access them in a sorted order.

    Second question, why are you writing your own sort routine?

    This design seems rather complicated. I understand the need for arbitratary storage of key value pairs, I designed a similar system in my book. However I am unclear as to the need for sorting those values nor the need for a custom sort routine such as this one.

    If you could explain the need behind the sorting I could probably suggest a better strategy.

    Also, I would highly recommend looking into the two methods -valueForUndefinedKey: and -setValue: forUndefinedKey: as a cleaner solution to your issue. That would allow you to write code like:

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

    and follow proper Key-Value Coding rules.

    Update

    The -valueForUndefinedKey: design is only for use in code, it is not for use accessing the store. I am still a little unclear with your goals.

    Given the following model:

    Entity <-->> Property
    

    In this design, Property has two attributes:

    Key
    Value
    

    From here you can access any property on Entity via -valueForUndefinedKey: because under the covers, Entity will go out and fetch the associated Property for that key. Thus you get dynamic values on your Entity.

    Now the question of sorting. With this design, you can sort directly on SQLite because you are really sorting on the Property entity. Although I am still unclear as to the final goal of the sorting. What value does it have? How will it be used?

    Update: Design reconsidered

    The last design I proposed was wrong. On deeper reflection, it is simpler than I proposed. Your goal can be accomplished with the original Entity <-->> Property design. However there is a bit more work to be done in the -setValue: forKey: method. The logic is as follows:

    1. External code called -setValue: forKey: on an Entity.
    2. The -setValue: forKey: method attempts to retrieve the Property.
    3. If the Property exists then the value is updated.
    4. If the Property does not exist then a Property is created for each Entity with a default value set (assumed to be an empty string).

    The only performance hit is when a new key is introduced. Other than that it should work without any performance penalties.

    这篇关于核心数据中的任意属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆