My FetchRequest is not aware of changes I made

  • Hi,

    Let Foo and Bar be two entities of my Core Data app. There is a one-to-
    many relationship between the two of them, Foo have bars and each Bar
    has a foo:
    Foo              Bar
    bars <--->> foo

    The Bar entity also has a boolean "isHappy" attribute.

    Here is what I do:

    - (IBAction)test:(id)sender {

    NSError *error;
    NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:[NSEntityDescription entityForName:@"Foo"
    inManagedObjectContext:managedObjectContext]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"ALL
    bars.isHappy = YES"]];

    NSArray *results = [managedObjectContext executeFetchRequest:request
    error:&error];
    NSLog(@"Found %d Foos with very happy Bars.", [results count]);

    for (NSManagedObject *foo in results)
      for (NSManagedObject *bar in [foo mutableSetValueForKey:@"bars"])
      [bar setValue:[NSNumber numberWithBool:NO] forKey:@"isHappy"];
    }

    Let's say that there are 3 Foos when my app launches, each of this
    Foos have happy bars.

    When test: is first called, the log says:
    "Found 3 Foos with very happy Bars."
    Then all the isHappy attributes are changed to NO, which is instantly
    visually verified in the GUI (the checkboxes get unchecked).

    But if I call test: again, the log says:
    "Found 3 Foos with very happy Bars."

    Why doesn't the fetch request finds 0 Foos? Why isn't the fetch
    request aware of the changes just made when the GUI is?
    The only thing that makes it work is to save the context after doing
    the changes, but I don't want to save at that point.

    This behaviour seems to go completely against the documentation: "If
    an object in a context has been modified, a predicate is evaluated
    against its modified state, not against the current state in the
    persistent store."

    Thanks,
    Martin.
  • Hi Martin,

    I don't have a complete answer for you, but I put together a project
    similar to yours and found a few things of interest:
    1) It appears to work correctly with Binary and In-Memory store types.
    2) Using XML store, I get the behavior you described.
    3) Using SQLite store, I get "Unsupported predicate ALL bars.isHappy
    == 1" in the console.

    Looking a little deeper with XML store, I added a second fetch:
    [request setEntity:[NSEntityDescription entityForName:@"Bar"
    inManagedObjectContext:managedObjectContext]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"isHappy =
    YES"]];
    NSArray *results2 = [managedObjectContext executeFetchRequest:request
    error:&error];
    NSLog(@"Found %d happy Bars.", [results2 count]);

    This fetch correctly returned the number of happy Bars, even while
    incorrectly fetching the number of Foos with all bars.isHappy=YES.

    I'll let you know if I figure anything else out, but I thought this
    might spark some productive thoughts...

    Wil

    On Feb 12, 2008, at 3:49 AM, Martin wrote:

    > Hi,
    >
    > Let Foo and Bar be two entities of my Core Data app. There is a one-
    > to-many relationship between the two of them, Foo have bars and each
    > Bar has a foo:
    > Foo              Bar
    > bars <--->> foo
    >
    > The Bar entity also has a boolean "isHappy" attribute.
    >
    > Here is what I do:
    >
    > - (IBAction)test:(id)sender {
    >
    > NSError *error;
    > NSFetchRequest *request = [[[NSFetchRequest alloc] init]
    > autorelease];
    > [request setEntity:[NSEntityDescription entityForName:@"Foo"
    > inManagedObjectContext:managedObjectContext]];
    > [request setPredicate:[NSPredicate predicateWithFormat:@"ALL
    > bars.isHappy = YES"]];
    >
    > NSArray *results = [managedObjectContext
    > executeFetchRequest:request error:&error];
    > NSLog(@"Found %d Foos with very happy Bars.", [results count]);
    >
    > for (NSManagedObject *foo in results)
    > for (NSManagedObject *bar in [foo mutableSetValueForKey:@"bars"])
    > [bar setValue:[NSNumber numberWithBool:NO] forKey:@"isHappy"];
    > }
    >
    > Let's say that there are 3 Foos when my app launches, each of this
    > Foos have happy bars.
    >
    > When test: is first called, the log says:
    > "Found 3 Foos with very happy Bars."
    > Then all the isHappy attributes are changed to NO, which is
    > instantly visually verified in the GUI (the checkboxes get unchecked).
    >
    > But if I call test: again, the log says:
    > "Found 3 Foos with very happy Bars."
    >
    > Why doesn't the fetch request finds 0 Foos? Why isn't the fetch
    > request aware of the changes just made when the GUI is?
    > The only thing that makes it work is to save the context after doing
    > the changes, but I don't want to save at that point.
    >
    > This behaviour seems to go completely against the documentation: "If
    > an object in a context has been modified, a predicate is evaluated
    > against its modified state, not against the current state in the
    > persistent store."
    >
    > Thanks,
    > Martin.
  • At 3:52 AM -0800 2/12/08, <cocoa-dev-request...> wrote:
    > NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    > [request setEntity:[NSEntityDescription entityForName:@"Foo"
    > inManagedObjectContext:managedObjectContext]];
    > [request setPredicate:[NSPredicate predicateWithFormat:@"ALL
    > bars.isHappy = YES"]];

    You're searching for Foo, but those objects are all unmodified.

    > Why doesn't the fetch request finds 0 Foos? Why isn't the fetch
    > request aware of the changes just made when the GUI is?

    The additional filtering of pending/unsaved changes is limited to the
    entity you fetch against (and its subentities).  In this case, any
    modified, inserted, or deleted (pending) Foo would display the
    behavior you want.

    > The only thing that makes it work is to save the context after doing
    > the changes, but I don't want to save at that point.

    You can evaluate the request against the Bar entity, and pull out the
    Foo across the inverse.  Or you can union the results from the fetch
    request with your own in memory filtering of all the registered
    objects in the MOC.  Or you can make this code call
    -willChangeValueForKey: on Foo to dirty them (but this will also
    cause them to get saved)
    --

    -Ben