核心数据中的任意属性 [英] Arbitrary Attributes in Core Data
问题描述
简而言之,我想在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
这有两个主要问题:
- 即使数据集很小,也很慢。
- 外部代码
-setValue:forKey:
Entity
。 -
-setValue:forKey:
Property
。 - 如果
Property
存在,则更新该值。 - 如果
属性
不存在,则会为每个属性创建属性
实体
使用默认值设置(假定为空字符串)。 - It's slow, even with small data sets.
- It can take an arbitrarily large amount of memory and hence crash with large data sets.
- External code called
-setValue: forKey:
on anEntity
. - The
-setValue: forKey:
method attempts to retrieve theProperty
. - If the
Property
exists then the value is updated. - If the
Property
does not exist then aProperty
is created for eachEntity
with a default value set (assumed to be an empty string).
我可以使用任何比撕开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:
方法中还有一些工作要做。逻辑如下:
唯一的性能命中是引入新密钥。除此之外,它应该工作,没有任何性能惩罚。
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:
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:
The only performance hit is when a new key is introduced. Other than that it should work without any performance penalties.
这篇关于核心数据中的任意属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!