查询Core Data中多个子实体类型的所有对象
Posted
技术标签:
【中文标题】查询Core Data中多个子实体类型的所有对象【英文标题】:Querying for all objects of multiple child entity types in Core Data 【发布时间】:2011-06-30 05:32:26 【问题描述】:给出以下人为的例子:
我想查询所有对象的数据,无论是猫还是狗。我想要按名称排序的结果集,而不考虑物种,因此先获取所有猫然后再获取所有狗是行不通的。我想在单个查询中执行此操作。
一种方法是向 Pet 添加一个 petType 字段,为每条记录赋予一个 petType 值来标识它所属的子实体,然后像这样查询:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Pet"
inManagedObjectContext:myMOC];
[fetchRequest setEntity:entity];
// petType values: 1 = dog, 2 = cat, 3 = goldfish. Yuk.
NSPredicate *p = [NSPredicate predicateWithFormat:@"petType = 1 OR petType = 2"]
[fetchRequest setPredicate:p];
// etc...
但是一想到要那样做就让我不寒而栗。有没有更好的办法?
更新:感谢所有回复的人 - 这里有一些非常好的、经过深思熟虑的解决方案,我很感激他们。
为了给出一些背景信息,真实的数据模型比这稍微复杂一些(并非总是如此),但它组织得很好。在我的时间里,我设计的数据模式超过了我应得的份额,我很高兴实体及其关系得到了很好的考虑。之所以出现这个问题,是因为(为了扩展已经摇摇欲坠的人为示例)客户最初想要的:
显示所有宠物列表的视图 显示金鱼列表的视图 显示猫列表的视图 显示狗列表的视图到目前为止,一切都很好。但他们也想要一个显示所有猫和狗的组合列表的视图,“因为小女孩喜欢猫和狗”。 (最初是猫和金鱼,出于同样的原因。)实际上并没有一种方法可以自然地对具体实体的子集进行分组。真是太随意了。
到目前为止,Dave Dribin 的“抽象中间实体”方法似乎是最干净的解决方案,尽管在我的情况下,我认为它会感觉有些人为;实际上,您可以真实地标记中间实体的唯一方法是“ThingLittleGirlsLike”! :)
【问题讨论】:
【参考方案1】:正如您所发现的,您不能在 SQLite 商店的 fetch 谓词中使用 entity.name
(您可以在其他商店类型上使用它)。你可以按照你的建议添加一个petType
,它工作得很好,但也让我不寒而栗。作为替代方案,您可以获取所有Pets
,然后根据entity.name
过滤获取的结果。但这有点低效,因为您加载的实体比您需要的多,并且您正在执行 SQLite 可能代表您执行的内存过滤。
我认为真正的问题是:你想做什么?如果你真的需要在没有Goldfish
的情况下获取Cats
和Dogs
,那么你的模型应该反映这一点。您可以插入一个FourLeggedPet
抽象实体作为Cat
和Dog
的父级。然后,在您的获取请求中,使用FourLeggedPet
作为带有setIncludesSubentities:YES
的实体。
【讨论】:
+1 此类问题通常是由糟糕的模型设计造成的。在这种情况下,宠物对象似乎与其他事物相关(当前未定义),例如一个所有者。如果您有所有者关系,那么您可以只要求所有者的宠物并完成。数据模型可能非常复杂,您应该利用它来密切建模/模拟您的应用程序处理的真实世界对象、条件和事件。一旦你这样做了,你通常会发现你的排序、谓词和提取会自动简化。 谢谢!我的人为示例到目前为止仅反映了我的真实用例,但我认为中间的 FourLeggedPet 实体想法对我来说效果很好。 (includesEntities
是 YES
默认情况下,顺便说一下 - 不需要设置它。)
@SimonWhitaker ,就我而言,我想获取所有子实体(GoldFish、猫和狗),但根据“邪恶”进一步过滤猫。有没有办法做到这一点?我使用的是 fetchedresultsController,所以我必须设置一个 fetchRequest。
@TechZen,请查看上面的评论。【参考方案2】:
您的托管对象已经拥有宠物类型:他们拥有自己的entity.name
。如果您搜索所有具有实体名称@"Cat"
或@"Dog"
的Pet
实体,我认为应该这样做。
示例(在 Xcode 中输入快速而肮脏,设计如此糟糕):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
// Insert code here to initialize your application
NSEntityDescription *catEntity = [NSEntityDescription entityForName: @"Cat" inManagedObjectContext: [self managedObjectContext]];
NSEntityDescription *dogEntity = [NSEntityDescription entityForName: @"Dog" inManagedObjectContext: [self managedObjectContext]];
NSEntityDescription *goldfishEntity = [NSEntityDescription entityForName: @"Goldfish" inManagedObjectContext: [self managedObjectContext]];
id cat = [[NSManagedObject alloc] initWithEntity: catEntity insertIntoManagedObjectContext: [self managedObjectContext]];
[cat setName: @"Mittens"];
id dog = [[NSManagedObject alloc] initWithEntity: dogEntity insertIntoManagedObjectContext: [self managedObjectContext]];
[dog setName: @"Rover"];
id fish = [[NSManagedObject alloc] initWithEntity: goldfishEntity insertIntoManagedObjectContext: [self managedObjectContext]];
[fish setName: @"Goldie"];
[[self managedObjectContext] save: NULL];
[fish release];
[dog release];
[cat release];
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName: @"Pet"];
NSPredicate *pred = [NSPredicate predicateWithBlock: ^(id evaluatedObject, NSDictionary *bindings)
BOOL result = ![[[evaluatedObject entity] name] isEqualToString: @"Goldfish"];
return result;
];
[fetch setPredicate: pred];
NSArray *entities = [[self managedObjectContext] executeFetchRequest: fetch error: NULL];
NSLog(@"Entities: %@", entities);
这会记录以下内容:
2011-06-30 14:46:57.435 CDSubentityTest[5626:407] 实体:( "(实体:猫;ID:0x10025f740 ;数据:\n 名称 = 手套;\n)", " (实体: 狗; id: 0x1002914e0 ;数据:\n name = Rover;\n)" )
注意金鱼的预期消失。
【讨论】:
NSInvalidArgumentException',原因:'keypath entity.name not found in entityentityName
是 NSManagedObject 类本身的属性,而不是数据模型中实体的属性。获取对实体属性而不是 NSManagedObject 类属性起作用。换句话说,没有entityName
值存储在持久存储中。该值在数据模型文件中。
@Graham Lee -- 谁会相信我或你自己说谎的眼睛。 ;-) 对错误的检查感到抱歉。【参考方案3】:
我相信您建议的方法会很好用。拥有petType
还可以让您定义Pet
s 的子类型(即“哺乳动物”、“鸟类”、“爬行动物”、“鱼”等)。当然,您可以为其中的每一个设置抽象实体,但也不清楚您将如何从中受益。这最终取决于您的应用程序如何使用模型。您是否需要获取所有Pet
s?您是否计划在一次提取中仅获得Cat
s 和Fish
?您是否关心获取不会立即使用的数据?
根据您想要显示/使用数据的方式定义所需的分类后,使模型适应这些分类。拥有一个好看的通用模型,使用起来通常会让人头疼……
【讨论】:
【参考方案4】:谓词无法识别实体,因此您无法构造谓词来查找它们。 (更新:这仅适用于 SQL 存储。)
如果您决定按宠物类型获取数据,那么您别无选择,只能提供一个属性,该属性将提供宠物类型值并允许谓词和获取操作。但是,您可以制作比您建议的版本更清洁、更安全的版本。
-
为每个实体创建一个
petType
属性(不能在实体中继承,但可以在自定义的NSManagedObject子类中继承。)
然后将数据模型编辑器中的默认值设置为物种的名称,例如猫、狗、金鱼等(如果您使用继承的属性,您也会继承默认值。)
重写 setter 方法以有效地使属性只读。 (这可以从一个公共超类继承。)
-(void) setPetType:(NSString *) petType
return;
现在查找所有狗和猫只需将获取实体设置为Pet
,然后为IN
运算符提供宠物类型名称数组。
NSArray *petTypes=[NSArray arrayWithObjects:@"cat",@"dog",nil];
NSPredicate *p=[NSPredicate predicateWithFormat:@"petType IN %@", petTypes];
虽然这行得通,但我认为Dave Dribin made the best point. 当您没有正确完善数据模型时,通常需要这种 hack。如果您需要按宠物类型对各种宠物进行分组,那么该分组可能属于另一个真实世界的对象、条件或事件,例如所有者,反过来又应该使用与特定宠物实例的关系来建模。如果你这样做,那么你的分组会自动发生,你不必搞乱上述所有事情。
【讨论】:
以上是关于查询Core Data中多个子实体类型的所有对象的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Core Data 中为多个对象(相同实体类型)生成数组实例?