CoreData: Same predicate (IN) returns different fe

2019-09-17 11:18发布

HUGE UPDATE:

I create a totally new project to mimic the problem, it does appear all the time!

Here is my data model:

enter image description here

enter image description here

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.

enter image description here

enter image description here

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.

2条回答
来,给爷笑一个
2楼-- · 2019-09-17 11:43

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 relationship watchers to Person, but Person doesn't have a inverse one to Task, then the relationship field watchers will be put in the Person table instead of a middle/mapping table.

Under this condition, my adding a Person p1 into the Task t1's watchers will update the watchers filed to 1 in the Person table.

Then, add Person p1 to the Task t2 as well, the watchers field will be update to 2 like below:

enter image description here

So, one Person can only point to one Task!

By adding a inverse relationship from Person to Task can walk around this problem.

查看更多
趁早两清
3楼-- · 2019-09-17 11:45

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?

查看更多
登录 后发表回答