madmik3
madmik3

Reputation: 6983

Query/Filter Core Data Entities with common parent whom are not key value complient

Is there a way create a predicate query to filter child entities? I would like to be able to return a single result set with filtering applied to the children.

For example I have Shape. And Circle an Square are shapes and I want to do this:

NSManagedObjectContext * context = [self managedObjectContext];

Circle * c = [NSEntityDescription insertNewObjectForEntityForName:@"Circle" inManagedObjectContext:context];
c.radius = @(10);

Square * s = [NSEntityDescription insertNewObjectForEntityForName:@"Square" inManagedObjectContext:context];
s.width = @(100);
s.height = @(200);


NSFetchRequest * f= [NSFetchRequest fetchRequestWithEntityName:@"Shape"];
f.predicate =[NSPredicate predicateWithFormat:@" radius = 10 OR hight = 200"];;
NSArray * results = [context executeFetchRequest:f error:nil];
NSLog(@"%@",results);

This produces a key value exception to be called when the fetch request is executed because a square does not have a radius, and a circle does not have a height and width.

Upvotes: 3

Views: 262

Answers (2)

Dan Shelly
Dan Shelly

Reputation: 6011

I would go with denormalizing the Shape entity:

Include all properties (which is the internal implementation of CoreData in any case [for a SQLite store]) in the Shape entity and only create implementations (@dynamic) for specialised shapes (Circle, Square, ...).
You can limit this denormalization to only properties you need to search by.

Notes:

  1. You might want to avoid setting a default value for the denormalized properties in the Shape entity, as the inheriting entities would still return value when using: [object valueForKey:@"somepropertynamefromspecializedclass"];. This way they would return nil for such keys.
  2. You might also want to add a shapeType property to your Shape entity so you might be able to limit your search for only specific types of Shapes

This way you only need a single fetch to get the objects you want and track them with a single FRC.

Example:

Shape: [abstract entity] (shapeType,name,radius,width,height): generate implementation for this entity (Shape.h and Shape.m) but remove the implementation for properties that were denormalized (radius,width,height) so your Shape .h file looks like:

@interface Shape : NSManagedObject
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * shapeType;
//radius ,width and height were removed (REMEMBER TO REMOVE THEM FROM YOUR .M FILE AS WELL)
@end

Shape .m file:

@implementation Shape
@dynamic name;
@dynamic shapeType;
@end

Circle: [inherit from Shape] ()

Circle .h file:

@interface Circle : Shape
@property (nonatomic,strong) NSNumber* radius;
@end

Circle .m file:

@implementation Circle
@dynamic radius;
@end

Rectangle: [inherit from Shape] () implemented very similarly to Circle

and now somewhere in your code you can do:

Circle* c = (Circle*)[NSEntityDescription insertNewObjectForEntityForName:@"Circle" inManagedObjectContext:self.managedObjectContext];
c.name = @"circle";
c.shapeType = @"c";
c.radius = @(100);

Rectangle* rect = (Rectangle*)[NSEntityDescription insertNewObjectForEntityForName:@"Rectangle" inManagedObjectContext:self.managedObjectContext];
rect.name = @"rectangle";
rect.shapeType = @"r";
rect.width = @(150);
rect.height = @(200);

[self.managedObjectContext save:nil];
[self.managedObjectContext reset];

NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:@"Shape"];
r.predicate = [NSPredicate predicateWithFormat:@"width > %@ OR radius > %@",@(100),@(90)];
NSArray* res = [self.managedObjectContext executeFetchRequest:r error:nil];

Upvotes: 2

Léo Natan
Léo Natan

Reputation: 57050

You can only achieve this by two ways - either add all possible properties to all entities (add radius to Square and width, height to Circle) or execute two fetch requests, one for circles and one for squares. I am for the second method.

NSFetchRequest * f = [NSFetchRequest fetchRequestWithEntityName:@"Circle"];
f.predicate =[NSPredicate predicateWithFormat:@" radius = 10"];
NSArray * results = [context executeFetchRequest:f error:nil];

if(results == nil) { results = @[]; }

f = [NSFetchRequest fetchRequestWithEntityName:@"Square"];
f.predicate = [NSPredicate predicateWithFormat:@"height = 200"];

results = [results arrayByAddingObjectsFromArray:[context executeFetchRequest:f error:nil]];

Upvotes: 2

Related Questions