查询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 的情况下获取CatsDogs,那么你的模型应该反映这一点。您可以插入一个FourLeggedPet 抽象实体作为CatDog 的父级。然后,在您的获​​取请求中,使用FourLeggedPet 作为带有setIncludesSubentities:YES 的实体。

【讨论】:

+1 此类问题通常是由糟糕的模型设计造成的。在这种情况下,宠物对象似乎与其他事物相关(当前未定义),例如一个所有者。如果您有所有者关系,那么您可以只要求所有者的宠物并完成。数据模型可能非常复杂,您应该利用它来密切建模/模拟您的应用程序处理的真实世界对象、条件和事件。一旦你这样做了,你通常会发现你的排序、谓词和提取会自动简化。 谢谢!我的人为示例到目前为止仅反映了我的真实用例,但我认为中间的 FourLeggedPet 实体想法对我来说效果很好。 (includesEntitiesYES 默认情况下,顺便说一下 - 不需要设置它。) @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 entity 啊,很好 - 谢谢。遗憾的是,您不能将块谓词与 SQLite 存储一起使用。 :( 你不能在 SQLite 存储中使用 entity.name,即使是非块谓词。 -1 -- 这不起作用,因为entityName 是 NSManagedObject 类本身的属性,而不是数据模型中实体的属性。获取对实体属性而不是 NSManagedObject 类属性起作用。换句话说,没有entityName 值存储在持久存储中。该值在数据模型文件中。 @Graham Lee -- 谁会相信我或你自己说谎的眼睛。 ;-) 对错误的检查感到抱歉。【参考方案3】:

我相信您建议的方法会很好用。拥有petType 还可以让您定义Pets 的子类型(即“哺乳动物”、“鸟类”、“爬行动物”、“鱼”等)。当然,您可以为其中的每一个设置抽象实体,但也不清楚您将如何从中受益。这最终取决于您的应用程序如何使用模型。您是否需要获取所有Pets?您是否计划在一次提取中仅获得Cats 和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 中为多个对象(相同实体类型)生成数组实例?

如何使用 Core Data 中多个实体的属性获取属性值?

在 Core Data 中获取过滤的关系

swift Core Data谓词过滤给定父级的所有子数据

Core Data Cocoa 错误 1570。不能保存多个实体对象

如果您想通过 Core Data 使用多个实体,您需要为每个实体提供一个托管对象上下文吗?