按对象ID对字典NSFetchRequest进行分组

时间:2015-11-12 12:47:58

标签: core-data nsfetchrequest

我需要返回一个对象列表及其相关对象的计数。在单个字典提取请求中似乎无法做到这一点,因为我无法按objectID对提取结果进行分组。

let objectIDExpression = NSExpressionDescription()
objectIDExpression.name = "objectID"
objectIDExpression.expression = NSExpression.expressionForEvaluatedObject()
objectIDExpression.expressionResultType = NSAttributeType.ObjectIDAttributeType

let countExpression = NSExpressionDescription()
countExpression.name = "count"
countExpression.expression = NSExpression(forFunction: "count:", arguments: [NSExpression(forKeyPath: "entries")])
countExpression.expressionResultType = .Integer32AttributeType

let fetchRequest = NSFetchRequest(entityName: "Tag")
fetchRequest.resultType = .DictionaryResultType
fetchRequest.propertiesToFetch = [objectIDExpression, countExpression]
fetchRequest.propertiesToGroupBy = [objectIDExpression]

var error: NSError?
if let results = self.context.executeFetchRequest(fetchRequest, error: &error) {
    println(results)
}

当此请求执行时,错误为:

'Invalid keypath expression ((<NSExpressionDescription: 0x7f843bf2d470>), name objectID, isOptional 1, isTransient 0, entity (null), renamingIdentifier objectID, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}) passed to setPropertiesToFetch:'

我还测试了传递"objectID"表达式名称,但也失败了。

因此无法按对象ID分组吗?

3 个答案:

答案 0 :(得分:1)

您可以在不使用propertiesToGroupBy 的情况下获得所需的计数。 CoreData似乎推断了计数的正确范围,并使用了一个子SELECT(奇怪的是,只有当fetch包含一个属性以及objectID和count时,见下文)。例如,我Tag多人Items

Data Model

首次尝试

我可以按如下方式获取tagName和项目数:

NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"Tag"];
NSExpressionDescription *countED = [NSExpressionDescription new];
countED.expression = [NSExpression expressionWithFormat:@"count:(items)"];
countED.name = @"countOfItems";
countED.expressionResultType = NSInteger64AttributeType;
fetch.resultType = NSDictionaryResultType;
fetch.propertiesToFetch = @[@"tagName", countED];
NSArray *results = [self.context executeFetchRequest:fetch error:nil];
NSLog(@"results is %@", results);

生成以下SQL:

SELECT t0.ZTAGNAME, (SELECT COUNT(t1.Z_3ITEMS) FROM Z_3TAGS t1 WHERE (t0.Z_PK = t1.Z_8TAGS) ) FROM ZTAG t0

第二次尝试

可悲的是,如果我尝试选择objectID而不是tagName,似乎CoreData会感到困惑:

NSExpressionDescription *selfED = [NSExpressionDescription new];
selfED.expression = [NSExpression expressionForEvaluatedObject];
selfED.name = @"self";
selfED.expressionResultType = NSObjectIDAttributeType;
fetch.resultType = NSDictionaryResultType;
fetch.propertiesToFetch = @[selfED, countED];

生成此SQL:

SELECT t0.Z_ENT, t0.Z_PK, COUNT( t1.Z_3ITEMS) FROM ZTAG t0 LEFT OUTER JOIN Z_3TAGS t1 ON t0.Z_PK = t1.Z_8TAGS

计算外部联接中的所有行(并建议您需要按objectID进行分组,但我们知道这不起作用)。

最后的尝试

但是,请添加tagName objectID,一切顺利:

fetch.propertiesToFetch = @[selfED, @"tagName", countED];

给出:

SELECT t0.Z_ENT, t0.Z_PK, t0.ZTAGNAME, (SELECT COUNT(t1.Z_3ITEMS) FROM Z_3TAGS t1 WHERE (t0.Z_PK = t1.Z_8TAGS) ) FROM ZTAG t0

似乎可以解决问题。 (很抱歉恢复到Objective-C,并使用不同的实体/属性名称,但我相信你能得到图片)。

<强>除了

我发现的另一个好奇心是上面的第二次尝试也可以通过计算关系的属性而不是关系本身来起作用:

countED.expression = [NSExpression expressionWithFormat:@"count:(items.itemName)"];
fetch.propertiesToFetch = @[selfED, countED];

给出:

SELECT t0.Z_ENT, t0.Z_PK, (SELECT COUNT(t2.ZITEMNAME) FROM Z_3TAGS t1 JOIN ZITEMS t2 ON t1.Z_3ITEMS = t2.Z_PK WHERE (t0.Z_PK = t1.Z_8TAGS) ) FROM ZTAG t0

这将(我认为)提供正确的计数itemName不是零。

答案 1 :(得分:0)

使用NSFetchedResultsController可能更容易。您可以将sectionNameKeyPath设置为分组,并使用生成的NSIndexPath来构建字典。

话虽如此,我认为按objectID分组是没有意义的,因为每个对象ID都是唯一的。所以每组中都会有一个实例。这可能是设置propertiesToGroupBy失败的原因。

所以,简短回答:不。

E.g。

let fetchRequest = NSFetchRequest(entityName: "Tag")
var output = [(NSManagedObjectID, Int)]()
do {
   let results = try context.executeFetchRequest(request) as! [Tag]
   for tag in results { 
       output.append((tag.objectID, tag.entries.count))
   }
} catch {}
// output contains tuples with objectID and count

如果entries是可选的,请使用tag.entries?.count ?? 0

答案 2 :(得分:0)

我玩了一点,确定必须有一些方法告诉核心数据按主键分组。

我无法理解,但我相信它是可能的。

我能做的最好的事情是添加另一个独特的属性“uuid”(我出于各种原因无论如何都用于我的所有实体)。您可以使用NSUUID轻松完成此操作,或者您可以使用永久对象ID URI表示并将其转换为字符串。

无论如何,我认为这会给你你想要的东西,但这需要一个独立的属性。

fetchRequest.propertiesToGroupBy = @[@"uuid"];

我尝试了一堆替代品作为分组属性,但expressionForEvaluatedObject总是barfs,其他尝试也没有。

我相信你已经知道了。为了以防万一,虽然它至少是一种解决方法,即使你不将它用于任何其他事情,直到有人出现之前实际上已经做过这件事。

FWIW,这是SQL ......

CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, COUNT( t1.Z_1ENTRIES), t0.ZUUID
    FROM ZTAG t0 LEFT OUTER JOIN Z_1TAGS t1 ON t0.Z_PK = t1.Z_2TAGS
    GROUP BY  t0.ZUUID

当然,必须有一种方法可以告诉它在分组条款中替换t0.Z_PK。我认为这应该是expressionForEvaluatedObject或“objectID”或“self”或“self.objectID”的简单特例

祝你好运,如果你解决了,请报告。我很感兴趣。