HUGE UPDATE:
I create a totally new project to mimic the problem, it does appear all the time!
Here is my data model:
Task
has a to-many relationship pointing to Person
.
@interface Person : NSManagedObject
@property (nonatomic, retain) NSString * personId;
@property (nonatomic, retain) NSString * name;
@end
//
@class Person;
@interface Task : NSManagedObject
@property (nonatomic, retain) NSNumber * taskId;
@property (nonatomic, retain) NSSet *watchers;
@end
@interface Task (CoreDataGeneratedAccessors)
- (void)addWatchersObject:(Person *)value;
- (void)removeWatchersObject:(Person *)value;
- (void)addWatchers:(NSSet *)values;
- (void)removeWatchers:(NSSet *)values;
@end
Now, I create two persons, p1 & p2, and two tasks, 1001 & 1002:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p1 = [self insertOrUpdatePerson:@"111" withName:@"aaa"];
Person *p2 = [self insertOrUpdatePerson:@"222" withName:@"bbb"];
[self insertOrUpdateTask:1001 withWatcher:p1];
[self insertOrUpdateTask:1001 withWatcher:p2];
[self insertOrUpdateTask:1002 withWatcher:p1];
[self insertOrUpdateTask:1002 withWatcher:p2];
NSArray *watchedTasks = nil;
watchedTasks = [self fetchTasksWatchedByPerson:p1]; // return 2 objects
NSLog(@"watchedTasks : %@\n", watchedTasks);
[self saveContext];
watchedTasks = [self fetchTasksWatchedByPerson:p1]; // return 1 objects
NSLog(@"watchedTasks : %@\n", watchedTasks);
NSArray *allTasks = [self fetchAllTasks]; // return 2 objects
NSLog(@"allTasks : %@\n", watchedTasks);
}
And the log follows. Notice that the second output of watchedTasks has only one object!
Printing description of watchedTasks:
<__NSArrayI 0x8d5aff0>(
<Task: 0x8d5b240> (entity: Task; id: 0x8d5b3e0 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p2> ; data: {
taskId = 1002;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
}),
<Task: 0x8d59d10> (entity: Task; id: 0x8d59420 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p1> ; data: {
taskId = 1001;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
})
)
Printing description of updateObjects:
<__NSArrayI 0x8e41880>(
<Task: 0x8d59d10> (entity: Task; id: 0x8d59420 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p1> ; data: {
taskId = 1001;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
}),
<Task: 0x8d5b240> (entity: Task; id: 0x8d5b3e0 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p2> ; data: {
taskId = 1002;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
}),
<Person: 0x8d56cc0> (entity: Person; id: 0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1> ; data: {
name = aaa;
personId = 111;
}),
<Person: 0x8d57be0> (entity: Person; id: 0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2> ; data: {
name = bbb;
personId = 222;
})
)
Printing description of watchedTasks:
<_PFArray 0x8f57da0>(
<Task: 0x8d5b240> (entity: Task; id: 0x8d5b3e0 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p2> ; data: {
taskId = 1002;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
})
)
Printing description of allTasks:
<_PFArray 0x8e44b30>(
<Task: 0x8d59d10> (entity: Task; id: 0x8d59420 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p1> ; data: {
taskId = 1001;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
}),
<Task: 0x8d5b240> (entity: Task; id: 0x8d5b3e0 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Task/p2> ; data: {
taskId = 1002;
watchers = (
"0x8d57f80 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p2>",
"0x8d56070 <x-coredata://706EB8CA-ACE7-48DB-89E1-7CCFCDD41DA4/Person/p1>"
);
})
)
(lldb)
So I open the .sqlite file to see what exactly in.
What surprise me first is that watchers
is in the Person
table, not in a middle/mapping Task2Person
table. I remember that once the relationship exists in a middle table.
Then, the watchers
field in the Person
table contains only one value, not a set or array!
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray *watchedTasks = nil;
Person *p1 = [self insertOrUpdatePerson:@"111" withName:@"aaa"];
Person *p2 = [self insertOrUpdatePerson:@"222" withName:@"bbb"];
watchedTasks = [self fetchTasksWatchedByPerson:p1]; // return 1 object
Task *t1 = [self insertOrUpdateTask:1001 withWatcher:p1]; // update watchers in the memory
[self insertOrUpdateTask:1001 withWatcher:p2];
Task *t2 = [self insertOrUpdateTask:1002 withWatcher:p1];
[self insertOrUpdateTask:1002 withWatcher:p2];
watchedTasks = [self fetchTasksWatchedByPerson:p1]; // return 2 objects
[self saveContext];
watchedTasks = [self fetchTasksWatchedByPerson:p1]; // return 1 object
So I found that after I launch the App and read the tasks from the db file, only 1 object is returned.
When I update the watchers
information for the tasks, 2 objects can be fetched.
Finally after I save the context, only 1 object again.
Just Ignore This
- Old Information Below
I have code below:
NSArray *existedTasks = [[TaskBizDB sharedInstance] fetchTasksWatchedByMeOfProject:projectId];
[context save:&error];
existedTasks = [[TaskBizDB sharedInstance] fetchTasksWatchedByMeOfProject:projectId];
NSArray *allTasks = [[TaskBizDB sharedInstance] fetchTasksOfProject:projectId];
- First line returns two objects;
- Second line save the context;
- Third line returns just one object, which is contained in the 'two objects' above;
- And the last line returns 6 objects, containing the 'two objects' returned at the first line.
The fetch interface works like below:
WXModel *model = [WXModel modelWithEntity:NSStringFromClass([WQPKTeamTask class])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(%@ IN personWatchers) AND (projectId == %d)", currentLoginUser, projectId];
[model setPredicate:predicate];
NSArray *fetchedTasks = [model fetch];
if (fetchedTasks.count == 0) return nil;
return fetchedTasks;
What confused me is that, with the same fetch request, why return different results just after a save?
Here comes more detail:
The 'two objects' returned at the first line are:
<WQPKTeamTask: 0x1b92fcc0> (entity: WQPKTeamTask; id: 0x1b9300f0 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p9> ; data: {
projectId = 372004;
taskId = 338001;
personWatchers = (
"0xf0bf440 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WWPerson/p1>"
);
}
<WQPKTeamTask: 0xf3f6130> (entity: WQPKTeamTask; id: 0xf3cb8d0 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p11> ; data: {
projectId = 372004;
taskId = 340006;
personWatchers = (
"0xf0bf440 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WWPerson/p1>"
);
}
And the only one object returned at third line is:
<WQPKTeamTask: 0x1b92fcc0> (entity: WQPKTeamTask; id: 0x1b9300f0 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p9> ; data: {
projectId = 372004;
taskId = 338001;
personWatchers = (
"0xf0bf440 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WWPerson/p1>"
);
}
Printing description of allTasks:
<_PFArray 0xf30b9a0>(
<WQPKTeamTask: 0xf3ab9d0> (entity: WQPKTeamTask; id: 0xf3cda40 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p6> ; data: <fault>),
<WQPKTeamTask: 0xf315720> (entity: WQPKTeamTask; id: 0xf3c23a0 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p7> ; data: <fault>),
<WQPKTeamTask: 0xf3a1ed0> (entity: WQPKTeamTask; id: 0xf3cda30 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p8> ; data: <fault>),
<WQPKTeamTask: 0x1b92fcc0> (entity: WQPKTeamTask; id: 0x1b9300f0 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p9> ; data: {
projectId = 372004;
taskId = 338001;
personWatchers = (
"0xf0bf440 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WWPerson/p1>"
);
}),
<WQPKTeamTask: 0xf325e50> (entity: WQPKTeamTask; id: 0xf343820 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p10> ; data: <fault>),
<WQPKTeamTask: 0xf3f6130> (entity: WQPKTeamTask; id: 0xf3cb8d0 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WQPKTeamTask/p11> ; data: {
projectId = 372004;
taskId = 340006;
personWatchers = (
"0xf0bf440 <x-coredata://CFFD3F8B-E613-4DE8-85AA-4D6DD08E88C5/WWPerson/p1>"
);
})
)
UPDATE 1
If I call the same interface fetchTasksWatchedByMeOfProject:
in:
#pragma mark - NSFetchedResultsController Delegate
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
I will get 'two objects' as well.
UPDATE 2
I've tried:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(ANY personWatchers == %@) AND (projectId == %d)", currentLoginUser, projectId];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(ANY personWatchers.personId == %@) AND (projectId == %d)", currentLoginUserId, projectId];
Still the same result.
UPDATE 3
I've checked the save:&error
, error is nil.
I've figured out this problem today with my teammates.
If you create a to-many relationship without inverse relationship, for example,
Task
has a to-many relationshipwatchers
toPerson
, butPerson
doesn't have a inverse one toTask
, then the relationship fieldwatchers
will be put in thePerson
table instead of a middle/mapping table.Under this condition, my adding a
Person
p1 into theTask
t1'swatchers
will update thewatchers
filed to1
in thePerson
table.Then, add
Person
p1 to theTask
t2 as well, thewatchers
field will be update to2
like below:So, one
Person
can only point to oneTask
!By adding a inverse relationship from
Person
toTask
can walk around this problem.What is being saved?
What happens when you print out the
-[NSManagedObjectContext updatedObjects]
before the save?What does the final predicate look like? Can you print the description of the
NSFetchRequest
?