在 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 中聚合子节点的主要内容,如果未能解决你的问题,请参考以下文章

jQuery DOM节点操作 - 父节点子节点兄弟节点

ztree获取当前选中节点子节点id集合的方法(转载)

是元素节点的属性节点子节点

如何从父 yaml 节点获取完整的 yaml 节点子对象?

如何同时更新不同的节点子节点firebase android

jQuery获取父节点子节点兄弟节点