在 Core Data 中聚合子节点
Posted
技术标签:
【中文标题】在 Core Data 中聚合子节点【英文标题】:Aggregating childs in Core Data 【发布时间】:2013-06-24 10:53:17 【问题描述】:我在使用 Core Data 的应用程序中创建获取请求时遇到了很大的问题。 我有 2 个实体,歌曲和流派。 我的模型如下所示:
我需要使用 Song 实体的 playCount 属性进行查询,以返回前 5 名播放艺术家。最后我需要按 Artist.name 属性对结果进行分组。
即结果字典应如下所示:
result_array('my_first_artist' => '3200', 'my_second_artist' => '12');
我知道我无法在 Core Data 中完成上述结果数组,但类似的东西会很棒。
我尝试使用此代码:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Artist" inManagedObjectContext:context]];
[request setPropertiesToFetch:@[@"name", @"songs.@sum.playCount"]];
[request setPropertiesToGroupBy:@[@"name"]];
[request setResultType:NSDictionaryResultType];
[request setSortDescriptors:@[[[NSSortDescriptor alloc] initWithKey:@"songs.@sum.playCount" ascending:NO]]];
[request setFetchLimit:5];
但它总是在@"songs.@sum.playCount"
部分抛出异常。
所以我的问题是:如何使用属性对关系对象求和?
任何建议都会很棒。
编辑:
感谢“Martin R”,部分问题得以解决。使用NSExpressionDescription
我可以进行提取,但NSSortDescriptor
不允许对不是NSManagedObject
实体属性的键进行排序。
如果有人需要,这是解决方案:
NSExpression *aggregateExpression = [NSExpression expressionForFunction:@"sum:"
arguments:@[[NSExpression expressionForKeyPath:@"songs.playCount"]]];
NSExpressionDescription *aggregateDescription = [[NSExpressionDescription alloc] init];
[aggregateDescription setName:@"count"];
[aggregateDescription setExpression:aggregateExpression];
[aggregateDescription setExpressionResultType:NSInteger32AttributeType];
[request setPropertiesToFetch:@[@"name", aggregateDescription]];
【问题讨论】:
从您的 CoreData 模型屏幕截图中,您的playCount
属性似乎不存在?
您谈论“歌曲”和“流派”,但图像显示“歌曲”和“艺术家”。 - 请显示确切的异常消息。
对不起,我上传了错误的图片。在编辑后的图像中,您可以看到 Song 实体具有 playCount 属性。这是我的异常消息:“由于未捕获的异常'NSInvalidArgumentException'而终止应用程序,原因:'对setPropertiesToFetch中的许多关系无效:(songs.@sum.playCount)'”
我认为你必须使用 NSExpressionDescription 作为 sum 表达式。我不确定按该表达式排序是否有效。
【参考方案1】:
我终于得到了答案。
我向 Genre.h 添加了新属性
@property (nonatomic, retain) NSNumber * songsPlayCountSum;
在 Genre.m 中我添加了
@dynamic songsPlayCountSum;
- (NSNumber *)songsPlayCountSum
return [self valueForKeyPath:@"songs.@sum.playCount"];
我还向我的 XCDATAMODEL 添加了名为 songPlayCountSum 的新属性。
这终于是我想要的代码了:
NSExpression *aggregateExpression = [NSExpression expressionForFunction:@"sum:"
arguments:@[[NSExpression expressionForKeyPath:@"songs.playCount"]]];
NSExpressionDescription *aggregateDescription = [[NSExpressionDescription alloc] init];
[aggregateDescription setName:@"count"];
[aggregateDescription setExpression:aggregateExpression];
[aggregateDescription setExpressionResultType:NSInteger32AttributeType];
NSManagedObjectContext *context = [self getManagedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Genre" inManagedObjectContext:context]];
[request setPropertiesToFetch:@[@"name", aggregateDescription]];
[request setPropertiesToGroupBy:@[@"name"]];
[request setPredicate:[NSPredicate predicateForNotEmptyAtrribute:@"name"]];
[request setResultType:NSDictionaryResultType];
[request setSortDescriptors:@[[[NSSortDescriptor alloc] initWithKey:@"songsPlayCountSum" ascending:NO]]];
[request setFetchLimit:5];
NSArray *results = [[self getManagedObjectContext] executeFetchRequest:request
error:nil];
编辑:
在 NSManagedObject 子类中使用自定义 getter 并不容易,所以当我想使用它时,我得到了错误的结果。 相反,将 KVO 添加到您的关系属性中,当 Core Data 对关系 NSSet 进行更改时,只需更新您需要用于排序的属性。就我而言,它看起来像这样:
#pragma mark - Memory Management
- (void) dealloc
[self removeObserver:self
forKeyPath:@"songs"];
#pragma mark - Initialization
- (id) initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context
if (self = [super initWithEntity:entity insertIntoManagedObjectContext:context])
[self addObserver:self
forKeyPath:@"songs"
options:0
context:NULL];
return self;
#pragma mark - Observing
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
if ([keyPath isEqualToString:@"songs"])
self.songsPlayCountSum = [self valueForKeyPath:@"songs.@sum.playCount"];
【讨论】:
【参考方案2】:也许您可以将瞬态(参见Documentation)属性songsPlayCount
添加到Genre
(或Artist
)。
使用它返回所有songs
中的playCount
。您可以使用此属性对输出进行排序。
【讨论】:
核心数据获取请求无法按瞬态属性排序。 我向 Artist / Genre 实体添加了 songPlayCountSum 属性。在每个实体 NSManagedObject 子类中使用 KVO,我用 sum “填充”属性,然后在 NSFetchRequest 中将其用作实际属性。请看下面我的回答。以上是关于在 Core Data 中聚合子节点的主要内容,如果未能解决你的问题,请参考以下文章