Query for all objects of several types of child entities in Core Data

Given the following contrived example:

Core Data entity diagram for my Awesome Pet Shop App (patent pending)

I would like to request my details for all objects that are either cats or dogs. I want the result set to be ordered by name, regardless of type, so fetching all cats that pull all dogs out will not. I want to do this in one request.

One way to do this is to add the PetType field to Pet, give each record a petType value that identifies the subsystem to which it belongs, then run the query as follows:

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... 

But the very thought of how this is done makes me shudder. Is there a better way?


Update : thanks to everyone who answered - there are really good, well-thought-out solutions here, and I appreciate them all.

To give this context, the actual data model is a little more complicated than this (not always), but it is pretty well organized. At one time, I developed more than my fair share of data schemas, and I'm glad that entities and their relationships are well considered. This problem arose because (to extend an already shaky far-fetched example) that the client originally wanted:

  • view showing a list of all pets.
  • view showing a list of goldfish
  • view showing a list of cats
  • view showing a list of dogs

So far so good. But they also want to see a general list of all cats and dogs "because little girls love cats and dogs." (For the same reason, at first it was cats and goldfish.) There really is no way to naturally group this subset of specific objects; it is really quite arbitrary.

Until now, Dave Dribin's "abstract intermediate entity" approach seems to be the purest solution, although in my case I think it will be somewhat artificial; in fact the only way you could honestly label an intermediate object would be "ThingLittleGirlsLike"! :)

+8
objective-c cocoa core-data
source share
4 answers

As you found out, you cannot use entity.name in your selection predicate for SQLite repositories (you can use it for other types of repository). You can add petType as you suggest, which works pretty well, but makes me tremble. Alternatively, you can get all the Pets and then filter the results based on entity.name . But this is a little inefficient, since you are loading more objects than you need and filtering in memory that SQLite can do on your behalf.

I think the real question is: what are you trying to do? If you really need to get Cats and Dogs without Goldfish , then your model should reflect that. You can insert an abstract FourLeggedPet object as the parent of Cat and Dog . Then in your fetch request, use FourLeggedPet as an object with setIncludesSubentities:YES .

+3
source share

Your managed entities already have a pet type: they have their entity.name . If you are looking for all Pet entities that have the object name @"Cat" or @"Dog" , this should do it, I think.

Example (typed quickly and dirty in Xcode, so poorly designed):

 - (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); } 

The following data is written here:

2011-06-30 14: 46: 57,435 CDSubentityTest [5626: 407] Entities: ("(object: Cat; id: 0x10025f740; data: {\ n name = Mittens; \ n})", "(entity: Dog; id: 0x1002914e0; data: {\ n name = Rover; \ n}) ")

Note the expected disappearance of the goldfish.

+2
source share

I believe that your approach will work well. Having petType can also allow you to define Pet subtypes (i.e., “Mammals,” “birds,” “reptiles,” “fish,” etc.). Of course, you can have abstract entities for each of them, but it is not clear how beneficial this is to you. Ultimately, it depends on how your application uses the model. Have you ever needed to get all Pet s? Do you plan to get only Cat and Fish in one sample? You do not like the data that you do not immediately use?

After determining the necessary classifications based on how you want to display / use the data, adapt the model to these classifications. Having a good, versatile model can often be a pain in the butt to use ...

0
source share

Predicates do not recognize entities, so you cannot construct a predicate to find them. (Update: this only applies to SQL repositories.)

If your heart is set to choose by type of pet, you have no choice but to provide an attribute that will provide the value of the type of animal, and which will allow predicates and samples to work. However, you can make a much cleaner and safer version than what you are offering.

  • Create a petType attribute for each object (it cannot be inherited in essence, but can be inherited in custom subclasses of NSManagedObject.)
  • Then set the default value in the data model editor to the view name, for example. cat, dog, goldfish, etc. (If you use the inherited attribute, you also inherit the default value.)
  • Override the setter method to do nothing efficiently by doing the readonly attribute. (This can be inherited from a common superclass.)

     -(void) setPetType:(NSString *) petType{ return; } 

Now finding all the dogs and cats just becomes a matter of setting the extraction object to Pet , and then providing and an array of animal type names for the IN operator.

 NSArray *petTypes=[NSArray arrayWithObjects:@"cat",@"dog",nil]; NSPredicate *p=[NSPredicate predicateWithFormat:@"petType IN %@", petTypes]; 

While this will work, I think Dave Dribin has made the best point. This type of hacking is usually necessary if you have not improved your data model. If you need to group different pets by type of pets, then this group probably refers to another object, condition or event in the real world, for example. owner, who, in turn, must be modeled with relationships to specific instances of pets. If you do this, then your grouping will happen automatically, and you do not have to bother with all of the above.

0
source share

All Articles