CoreData fetch 基于谓词添加属性

Posted

技术标签:

【中文标题】CoreData fetch 基于谓词添加属性【英文标题】:CoreData fetch adding an attribute based on a predicate 【发布时间】:2014-09-24 09:04:04 【问题描述】:

我有以下数据模型(简化):

WordEntity          ListItemEntity          ListEntity
----------          --------------          ----------
text                                        name
----------          --------------          ----------
listItems <------>> word
                    list <<---------------> items

以及以下基本查询:

let fetchRequest = NSFetchRequest(entityName: "WordEntity")
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)

let list = <ListEntity instance>
... do something clever here ...
controller.performFetch(nil)

我需要获取的结果为每个对象包含一个属性inList,如果该WordEntity 对象附加到ListItemEntity,而ListItemEntity 又附加到list,这将是正确的。类似于以下谓词,除了用于创建新属性而不是过滤获取请求:

NSPredicate(format: "ANY listItems.list == %@", list)

我查看了 NSExpressions,但它们似乎只用于聚合,不能做谓词。 SUBQUERY 可以做谓词,但仅用于过滤结果集。并且计算出的瞬态属性可以进行任何我想要的查找,但是它们无法对外部值进行操作...

我希望这很清楚......在此先感谢。

更新:

正如 pbasdf 建议的那样,我能够使用获取的属性来做到这一点。我在WordEntity 上有一个带有谓词的属性listItemsInList

(word == $FETCH_SOURCE) AND (list == $FETCHED_PROPERTY.userInfo.list)

然后在代码中:

let request = NSFetchRequest()
let entity = NSEntityDescription.entityForName("WordEntity", inManagedObjectContext: context)!
request.entity = entity
for property in entity.properties 
    if property.name == "listItemsInList" 
        let list = <ListEntity instance>
        (property as NSFetchedPropertyDescription).userInfo!["list"] = list
    

最后:

if word.listItemsInList.count > 0 
    ... this is what I was looking for ...

这行得通。不幸的是,它的效率很低。获取的属性总是返回获取对象的数组,而不是计算值。他们总是获取整个对象。最糟糕的是,它们不能被预取,因此检查表格单元格中的属性意味着每行都会命中数据库。所以我仍然希望有更聪明的方法来做到这一点。

【问题讨论】:

您查看过获取的属性吗? 谢谢。我不认为它对获取的属性是可行的,但我再次尝试了你的建议,我让它工作了。我已经用详细信息更新了问题。 如果您只是想检查给定单词是否在给定列表中,请尝试if ([word.listItems intersectsSet:list.items]) ...(抱歉,Objective-C)。 谢谢。没关系,我可以阅读Objective-C。起初我是这样做的,只是检查一下设置,但我认为应该有更好的方法来做到这一点。我认为,如果我可以将它放入获取单词列表的同一个 CoreData 查询中,那将是最有效的。 【参考方案1】:

经过一番反复试验,我找到了一个解决方案,只要您对“按值”返回的实体属性感到满意(即它们将是基础属性)在字典数组中。一个节省的优点是字典可以包含相关对象的 CoreData objectID,因此检索该对象相对简单。

诀窍是使用一个 NSExpression,其中包括一个 SUBQUERY(用相关的列表名称替换)和@count。这是我使用的(抱歉,又是 Obj-C!):

// First, get an NSAttribute description for each of the attributes we want in our query
NSEntityDescription* entity = [NSEntityDescription entityForName:@"WordEntity" inManagedObjectContext:self.context];
NSAttributeDescription *wordDesc = [entity.attributesByName objectForKey:@"word"];

// Also get an NSExpression description for the object itself
NSExpression *objIDExpression = [NSExpression expressionForEvaluatedObject];
NSExpressionDescription *objIDDescription = [[NSExpressionDescription alloc] init];
[objIDDescription setName: @"cdObjectID"];
[objIDDescription setExpression: objIDExpression];
[objIDDescription setExpressionResultType: NSObjectIDAttributeType];

// Define an expression to count a subquery
NSExpression *countExpression = [NSExpression expressionWithFormat:@"SUBQUERY(listItems,$x,$x.list.listName like %@).@count",@"Animals",nil]; 
// Note, in the above, replace @"Animals" with list.listName for the list of interest

NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: @"count"];
[expressionDescription setExpression: countExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];

NSFetchRequest* fetch = [NSFetchRequest fetchRequestWithEntityName:@"WordEntity"];
[fetch setPropertiesToFetch:@[wordDesc, objIDDescription, expressionDescription]];
[fetch setResultType:NSDictionaryResultType];
NSArray *myResults = [self.context executeFetchRequest:fetch error:&error];
NSLog(@"myResults: %@",myResults);
// Recover the actual WordEntity object for the first item in myResults:
WordEntity *myWord = (WordEntity *)[self.context objectWithID:[[myResults firstObject] valueForKey:@"cdObjectID"]];

myResults 的输出如下所示。我的测试数据包括我在名为 Animals、Food 等的列表中包含的各种单词。下面的输出是针对 Animals 的;注意 count=1 for Dog!

    
    cdObjectID = "0xd000000000040004 <x-coredata://4B6BC796-CAEF-4B0C-9B27-BC6CCDA572C7/WordEntity/p1>";
    count = 0;
    word = Orange;
,
    
    cdObjectID = "0xd000000000080004 <x-coredata://4B6BC796-CAEF-4B0C-9B27-BC6CCDA572C7/WordEntity/p2>";
    count = 0;
    word = Geranium;
,
    
    cdObjectID = "0xd0000000000c0004 <x-coredata://4B6BC796-CAEF-4B0C-9B27-BC6CCDA572C7/WordEntity/p3>";
    count = 0;
    word = Banana;
,
    
    cdObjectID = "0xd000000000100004 <x-coredata://4B6BC796-CAEF-4B0C-9B27-BC6CCDA572C7/WordEntity/p4>";
    count = 1;
    word = Dog;
,
    
    cdObjectID = "0xd000000000140004 <x-coredata://4B6BC796-CAEF-4B0C-9B27-BC6CCDA572C7/WordEntity/p5>";
    count = 0;
    word = Apple;
,
    
    cdObjectID = "0xd000000000180004 <x-coredata://4B6BC796-CAEF-4B0C-9B27-BC6CCDA572C7/WordEntity/p6>";
    count = 1;
    word = Elephant;
, ....

我不知道在性能方面如何与其他解决方案进行比较!

【讨论】:

谢谢!这正是我希望能够做到的事情。

以上是关于CoreData fetch 基于谓词添加属性的主要内容,如果未能解决你的问题,请参考以下文章

带有函数参数的 CoreData 谓词

在没有 tableView 的情况下使用带有 fetch 的谓词

核心数据:基于谓词的验证

比较过多关系中的两个属性的 CoreData 谓词

基于实体属性确保 CoreData 实体唯一的最佳实践

CoreData fetch() 返回无序对象