iOS Core Data complex predicate.

  • Greetings!

    I am facing a problem with a complicated predicate I'm building.
    it keeps returning multiple instances of the same records.
    if every predicate used alone works as a charm, problems occur when they are combined together.

    my model is constructed as so
    ------------------------------------
    Table 1 contains most descriptive fields.
    name
    description
    additional information.

    Table 2 contains the categories
    name for the category
    keyword

    Table 3 contains elements
    name of the element
    keyword

    table 1 has a to-many relationship to table 2
    table 2 has a to many inverse relationship to table 1

    table 1 has a to-many relationship to table 3
    table 3 has a to many inverse relationship to table 1

    -one item from table 1 can belong to many categories and one category can be assigned to multiple items from table 1
    -one item from table 1 can have many elements, and one element can belong to many items of table 1;

    ----------------------------
    My search criteria's are as so.
    user can enter keywords to search the tiems.
    user can select from a list of categories the categories to search for.
    user can select fro ma list of elements the elements to include in the search.

    The keywords are separated in components and filtered against a StopWord array as to keep only the relevant words to build the predicate.
    each keyword is OR'd against three search field.
    and then AND together as to assure all keywords appear in one particular item of table 1

    if ([self.searchText.text length] > 0){
      NSArray * components = [self.searchText.text componentsSeparatedByString:@" "];
      for (NSString *component in components){
      if (!isInFrenchStopWords(component)){
        NSMutableArray *orSubPredicates = [NSMutableArray new];
        [orSubPredicates addObject:[NSPredicate predicateWithFormat:@"%@ IN[cd] self.name",component]];
        [orSubPredicates addObject:[NSPredicate predicateWithFormat:@"%@ IN[cd] self.description",component]];
        [orSubPredicates addObject:[NSPredicate predicateWithFormat:@"%@ IN[cd] self.addInfo",component]];
        // build a OR Predicate for this particular keyword.
        [keywordsSubPredicates addObject:[NSCompoundPredicate orPredicateWithSubpredicates:orSubPredicates]];
        [orSubPredicates release];
      }
      }
      // keywords are combined in a AND predicate, all keywords must have a hit.
      [finalSubPredicates addObject:[NSCompoundPredicate andPredicateWithSubpredicates:keywordsSubPredicates]];
    }

    Then if there are selected categories these are also predicated as so.
    the selected categories ManagedObject are stored in an NSDictionary

    A predicate is built for every selected category, because it is a many to many relationship.
    i've tried using category.name but that did not work.

    if ([self.categoryPredicateDict count]){
      NSArray * components = [self.categoryPredicateDict allValues];
      for (ModelCategory *component in components){
      [categoriesSubPredicates addObject:[NSPredicate predicateWithFormat:@"%@ IN[cd] categories",component]];
      }
      // or any selected Category
      [finalSubPredicates addObject:[NSCompoundPredicate orPredicateWithSubpredicates:categoriesSubPredicates]];
    }

    A predicate is built for every selected element, because it is a many to many relationship.

    if ([self.elementPredicateDict count]){
      NSArray * components = [self.elementPredicateDict allValues];
      for (ModelElement *component in components){
      [elementSubPredicates addObject:[NSPredicate predicateWithFormat:@"%@ IN[cd] elements",component]];
      }
      // or any selected elements
      [finalSubPredicates addObject:[NSCompoundPredicate orPredicateWithSubpredicates:elementsSubPredicates]];
    }

    and then to finalize everything and put it all together.

    return [NSCompoundPredicate andPredicateWithSubpredicates:finalSubPredicates];

    then this predicate is fed to a fetch Request which in turn is fed to a fetched Result Controller.
    the fetch is configured to    [fetchRequest setReturnsDistinctResults:YES];

    The problem as stated in the introduction this complete predicate returns multiple instances of the same record and it should not.
    if any of you see a flaw in the logic or anything, please comment, I'm currently so deep into it, I cant see it anymore. :)

    I hope I have not missed nay details.
    Thank you!!!
    Sandro.
  • On Mar 17, 2011, at 8:15 PM, Sandro Noël wrote:

    > Greetings!
    >
    > I am facing a problem with a complicated predicate I'm building.
    >
    ...

    > then this predicate is fed to a fetch Request which in turn is fed to a fetched Result Controller.
    > the fetch is configured to    [fetchRequest setReturnsDistinctResults:YES];
    >
    > The problem as stated in the introduction this complete predicate returns multiple instances of the same record and it should not.

    About setReturnsDistinctResults:
    "If YES, the request returns only distinct values for the *fields specified by propertiesToFetch*."

    That is, setReturnDistinctResults: makes only sense IFF you also specified the set of properties you want to fetch (via -setPropertiesToFetch). And this requires that you specify NSDictionaryResultType for the resultType property of the fetch request. As a result, with setReturnDistinctResults:YES you get a set of dictionaries whose values are distinct.

    In other words, you cannot use -setReturnsDistinctResults:YES to make your result set distinct if this array contains *managed objects*.

    You might use a NSSet which you initialize from your original array of managed objects in order to get a unique set.

    Alternatively, you might fetch just objectID properties using a NSDictionaryResultType. This, however, is a bit elaborated:

    When you return (unique) dictionaries as objects in your result array, and if you want these dictionaries having a key "objectID" which value corresponds the actual managed object of this dictionary instance, you need to create an appropriate NSExpressionDescription: and include this property description in the array which you pass to -setPropertiesToFetch:

    NSExpressionDescription* objectIdDescription = [[NSExpressionDescription alloc] init];
    [objectIdDescription setExpression:[NSExpression expressionForEvaluatedObject]];
    [objectIdDescription setExpressionResultType:NSObjectIDAttributeType];

    Note: NSExpressionDescription is subclassed from NSPropertyDescription.

    Then you use objectIdDescription as one of the properties (NSPropertyDescription) you want to fetch:
    [myFetchRequest setPropertiesToFetch:[NSArray arrayWithObject: objectIdDescription]];

    Don't forget to set the result type (after you set the entity):
    [myFetchRequest setResultType: NSDictionaryResultType];
    [myFetchRequest setReturnsDistinctResults:YES];
    ...

    The next step would be to extract the objectIDs from the array of dictionaries and store them in an array for convenience.

    Regards
    Andreas

    > if any of you see a flaw in the logic or anything, please comment, I'm currently so deep into it, I cant see it anymore. :)
    >
    > I hope I have not missed nay details.
    > Thank you!!!
    > Sandro.
  • Andreas

    First off, thank you for taking the time to read my essay.

    I did not know the setReturnDistinctResults: was dependant on setPropertiesToFetch:
    your explanation is outstanding.

    That did the trick, and I thank you very much.

    Sandro.

    On 2011-03-17, at 6:49 PM, Andreas Grosam wrote:

    >
    > On Mar 17, 2011, at 8:15 PM, Sandro Noël wrote:
    >
    >> Greetings!
    >>
    >> I am facing a problem with a complicated predicate I'm building.
    >>
    > ...
    >
    >> then this predicate is fed to a fetch Request which in turn is fed to a fetched Result Controller.
    >> the fetch is configured to    [fetchRequest setReturnsDistinctResults:YES];
    >>
    >> The problem as stated in the introduction this complete predicate returns multiple instances of the same record and it should not.
    >
    > About setReturnsDistinctResults:
    > "If YES, the request returns only distinct values for the *fields specified by propertiesToFetch*."
    >
    > That is, setReturnDistinctResults: makes only sense IFF you also specified the set of properties you want to fetch (via -setPropertiesToFetch). And this requires that you specify NSDictionaryResultType for the resultType property of the fetch request. As a result, with setReturnDistinctResults:YES you get a set of dictionaries whose values are distinct.
    >
    > In other words, you cannot use -setReturnsDistinctResults:YES to make your result set distinct if this array contains *managed objects*.
    >
    >
    > You might use a NSSet which you initialize from your original array of managed objects in order to get a unique set.
    >
    > Alternatively, you might fetch just objectID properties using a NSDictionaryResultType. This, however, is a bit elaborated:
    >
    > When you return (unique) dictionaries as objects in your result array, and if you want these dictionaries having a key "objectID" which value corresponds the actual managed object of this dictionary instance, you need to create an appropriate NSExpressionDescription: and include this property description in the array which you pass to -setPropertiesToFetch:
    >
    > NSExpressionDescription* objectIdDescription = [[NSExpressionDescription alloc] init];
    > [objectIdDescription setExpression:[NSExpression expressionForEvaluatedObject]];
    > [objectIdDescription setExpressionResultType:NSObjectIDAttributeType];
    >
    > Note: NSExpressionDescription is subclassed from NSPropertyDescription.
    >
    > Then you use objectIdDescription as one of the properties (NSPropertyDescription) you want to fetch:
    > [myFetchRequest setPropertiesToFetch:[NSArray arrayWithObject: objectIdDescription]];
    >
    > Don't forget to set the result type (after you set the entity):
    > [myFetchRequest setResultType: NSDictionaryResultType];
    > [myFetchRequest setReturnsDistinctResults:YES];
    > ...
    >
    >
    > The next step would be to extract the objectIDs from the array of dictionaries and store them in an array for convenience.
    >
    >
    > Regards
    > Andreas
    >
    >
    >> if any of you see a flaw in the logic or anything, please comment, I'm currently so deep into it, I cant see it anymore. :)
    >>
    >> I hope I have not missed nay details.
    >> Thank you!!!
    >> Sandro.

previous month march 2011 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today