5

I'm trying to get a distinct result from NSPredicate.

My code:

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Members" inManagedObjectContext:context];
    request.entity = entity;
    request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"city"
                                                                                     ascending:YES
                                                                                      selector:@selector(caseInsensitiveCompare:)]];

    request.predicate = [NSPredicate predicateWithFormat:@"memberDeleted == %@", [NSNumber numberWithBool:NO]];

    NSDictionary *properties = [entity propertiesByName];
    request.propertiesToFetch = [NSArray arrayWithObject:[properties objectForKey:@"city"]];
    request.returnsDistinctResults = YES;

    request.fetchBatchSize = 20;

    NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                          managedObjectContext:context
                                                                            sectionNameKeyPath:nil
                                                                                     cacheName:@"CityCache"];
    [request release];

    self.fetchedResultsController = frc;
    [frc release];

The issue is that the result returns many times the same City. This Entity has a lot of Members, and each Member has a property "city".

What am I doing wrong?

Thanks,

RL

Rui Lopes
  • 2,562
  • 6
  • 34
  • 49

3 Answers3

7

Make sure to set the resultType of the NSFetchRequest to NSDictionaryResultType. The default is to return the actual objects, and so it will ignore propertiesToFetch.

Alex
  • 26,829
  • 3
  • 55
  • 74
  • I should probably add that `NSFetchedResultsController` is not generally a fan of the `NSDictionaryResultType`. You may run into other problems and have to abandon the `NSFetchedResultsController`. – Alex Oct 08 '11 at 18:57
  • The question is that I don't want the result to be and NSDictionary. If not, I'll have to change some of my code. Is it doable other way? – Rui Lopes Oct 08 '11 at 19:45
  • What object are you expecting it to return, then? Core Data can only return objects that actually exist in your object graph. – Alex Oct 08 '11 at 19:51
  • I've got it. You are correct. I was thinking in getting the Members, and group them by city. I do not have a City object, so your suggestion is correct. My thought was wrong. Not use the NSFetchedResultsController is the best choice. Can you update the answer, so that other see it better? – Rui Lopes Oct 08 '11 at 19:54
6

With the help of @Alex, here's the final code, without FRC:

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Members" inManagedObjectContext:self.managedObjectContext];
request.entity = entity;
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"city"
                                                                                 ascending:YES
                                                                                  selector:@selector(caseInsensitiveCompare:)]];

request.predicate = [NSPredicate predicateWithFormat:@"memberDeleted == %@", [NSNumber numberWithBool:NO]];

NSDictionary *properties = [entity propertiesByName];
request.propertiesToFetch = [NSArray arrayWithObject:[properties objectForKey:@"city"]];
request.returnsDistinctResults = YES;
request.resultType = NSDictionaryResultType;
request.fetchBatchSize = 20;

NSError *error = nil;
NSArray *tempArray = [self.managedObjectContext executeFetchRequest:request error:&error];

NSMutableArray *cities = [[NSMutableArray alloc] init];
for (int i=0; i < [tempArray count]; i++){
    NSDictionary *tempDict = [NSDictionary dictionary];
    tempDict = [tempArray objectAtIndex:i];

    if ([tempDict objectForKey:@"city"] != nil)
        [cities addObject:[tempDict objectForKey:@"city"]];
}

//if the tempArray has no nil values, it's more efficient with:
//NSArray* cities = [tempArray valueForKeyPath:@"city"];

self.cityArray = cities;
[cities release];
[request release];

This returns a NSArray with the list of the Cities.

Thanks.

Rui Lopes
  • 2,562
  • 6
  • 34
  • 49
  • 1
    You can do this more easily. Just do this: `NSArray* cities = [tempArray valueForKeyPath:@"city"];` This will get the value of `city` for each dictionary in the array and put them in an array. – Alex Oct 08 '11 at 21:26
  • 1
    @Alex, that works fine if there's no nil value in the Array. If there is any nil value, it crash. – Rui Lopes Oct 09 '11 at 13:29
  • Oops. I upvoted Rui's "crash" comment while investigating this issue, thinking it a valuable caution. But the NSArray class ref lists a slightly different method, "valueForKey:", which it says will return an NSNull object for nil objects. So there shouldn't be a problem. – Wienke Feb 18 '12 at 16:30
3

From the official documentation:

NSManagedObjectContext *context = <#Get the context#>;

NSEntityDescription *entity = [NSEntityDescription  entityForName:@"<#Entity name#>" inManagedObjectContext:context];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setReturnsDistinctResults:YES];
[request setPropertiesToFetch :[NSArray arrayWithObject:@"<#Attribute name#>"]];

// Execute the fetch.
NSError *error;
id requestedValue = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
    // Handle the error.
}
xster
  • 6,269
  • 9
  • 55
  • 60