views:

957

answers:

2

I have a "Song" Entity and a "Tag" entity and they have a many to many relationship between them. A Song can have multiple Tags and a Tag can be applied to multiple Songs.

I want to check if a Song has a particular Tag associated with it. If the Song has the Tag associted with it, I want to show a checkmark in the table view.

For a similar logic, in Apple "TaggedLocations" sample code, the following check is made to check for the presence of the relationship.

if ([event.tags containsObject:tag]) {
    cell.accessoryType = UITableViewCellAccessoryCheckmark;
}

This may be inefficient if there are a lot of Tags in the database as this will fetch all of them in the memory. Please correct me if I am wrong here.

Is there a more efficient way to check if the Song is associated with a particular Tag instead of checking in Song.Tags?

+3  A: 

You are correct, using that code will retrieve the entire set and the object comparison may be quite complex, depending on how many properties and relationship are part of the object's entity.

Anyway, you can not avoid a set comparison for inclusion. Probably, the best you can do is to avoid fetching all of the properties/relationships by asking Core Data to retrieve NSManagedObjectID Objects only.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Tag" inManagedObjectContext:[self managedObjectContext]]]; 
[fetchRequest setResultType:NSManagedObjectIDResultType];

NSManagedObjectID objects are guaranteed to be unique, therefore you can safely use them to check for set inclusion. This should be much more efficient for a performance perspective.

unforgiven
Thanks for the explanation.IMHO, it would still mean fetching and keeping all the associated Tag objects in memory at the same time. What if there are 1000 of associated Tags in the database?In my present non core data implementation, I have an intermediate join table to check for the presence of the association.Core Data document says core data takes care of the intermediate join table for many to many relationship? Is there a way to write a predicate to use the intermediate join table properties and avoid reading all the objects in the memory?
siasl
You can't take advantage of the intermediate join table. It is transparently used internally by Core Data. There is a special case possible, in which you add explicitly a third intermediate entity mediating between the two original entities, as described in Apple "Core Data Programming Guide", section named "Relationships and Fetched Properties", and within this section take a look at "Many-to-Many Relationships". You must decide if your Songs/Tags relationships can be modeled as in their friends example (probably not).
unforgiven
Yeah Songs/Tags does not really fit into that model, sigh!
siasl
+2  A: 

It's actually pretty easy to do, if completely undocumented. You want to create a fetch request with a predicate that has a set operation. If we imagine that your Tag model has a property called tagValue, the predicate you care about is "ANY tags.tagValue == 'footag'"

NSString *tagSearch = @"footag";

// However you get your NSManagedObjectContext.  If you use template code, it's from
// the UIApplicationDelegate
NSManagedObjectContext *context = [delegate managedObjectContext];

// Is there no shortcut for this?  Maybe not, seems to be per context...
NSEntityDescription *songEntity = [NSEntityDescription entityForName:@"Song" inManagedObjectContext:context];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:songEntity];

// The request looks for this a group with the supplied name
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY tags.tagValue == %@", tagSearch];
[request setPredicate:predicate];

NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];

[request release];
Douglas Mayle