核心数据提取在第二次使用谓词时返回0结果

时间:2014-01-17 19:02:03

标签: ios objective-c core-data

希望您可以帮助解决核心数据应用中存在的问题。我把它归结为基本要素:

这是psudocode(这篇文章中要遵循的真实代码):

[run a fetch request] // returns 18 objects
[save context]        // no NSError, returns YES - success
[run a fetch request] // returns 0 objects; request is exactly the same request as previous

如果我不保存,则两个提取请求都返回18个对象。

获取请求是在一个单独的方法中完成的,所以我确信请求两次都是相同的。请求使用此NSPredicate:

   NSPredicate *predicateFailsAfterSave = [NSPredicate predicateWithFormat:@"NOT (%@ IN games)", self.game]; // where THISGAME is not IN self.games

如果我将此谓词更改为更简单的内容,例如“fullName CONTAINS%@”,@“Frank”,那么即使我包含save命令,它也会同时工作。该谓词应返回尚未分配给self.Game对象的所有Guest。它第一次工作,如果我保存则返回0。

该模型由两个实体组成:

  • “来宾”,实体。 1属性(fullName,String); 1关系(To Many;“游戏”,与Game相反,称为“guest”)

  • “游戏”,实体。 0属性。 1关系(To Many;“guest”,与Guest相反,称为“游戏”)

流程:

  1. 通过在加载视图时解析文本文件来创建Guest对象。设置了“fullName”值。

    NSArray * namesArray = //这是一个字符串数组

    for (NSString *playerName in namesArray) {
        NSManagedObject *newPlayer = [NSEntityDescription insertNewObjectForEntityForName:@"Guest"
                                                                   inManagedObjectContext:self.managedObjectContext];
        [newPlayer setValue:playerName forKey:@"fullName"];
        NSLog(@"Created %@", [newPlayer valueForKey:@"fullName"]);
    }
    
  2. 在制作访客对象时,游戏对象尚未实例化。 (对于整个问题,不存在Game和Guest之间的关系将被设置的情况。)然后创建Game对象,此时它的Guests关系中没有对象:

    - (void)viewWillAppear:(BOOL)animated {     [super viewWillAppear:animated];

                [[NSNotificationCenter defaultCenter] addObserver:self
               selector:@selector(mocChanged:)
            name:NSManagedObjectContextObjectsDidChangeNotification
                                                           object:nil];
    
                _game = [NSEntityDescription insertNewObjectForEntityForName:@"Game"
                                                            inManagedObjectContext:self.managedObjectContext];
    
                // testing
                [self testFetch];
    
                /// try save
                NSError *error = nil;
                if ([self.managedObjectContext save:&error] == NO) {
                    NSLog(@"Error: %s: %@", __PRETTY_FUNCTION__, error.localizedDescription);
                }
    
                // re-do fetch
                [self testFetch];
            }
    
            -(void)mocChanged:(NSNotification *)notification {
                NSLog(@"notif: %@", notification.userInfo);
            }
    
  3. 您可以看到我发送了两次testFetch消息,其中“save”位于它们之间。如果我注释掉保存,它们都会返回相同的结果。添加保存,后者返回0个对象。这是testFetch:

        -(void)testFetch {
            NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Guest"];
            NSPredicate *testKnownGoodPredicate = [NSPredicate predicateWithFormat:@"fullName CONTAINS[cd] %@", @"wood"];
            NSPredicate *predicateFailsAfterSave = [NSPredicate predicateWithFormat:@"NOT (%@ IN games)", self.game]; // where THISGAME is not IN self.games
    
            request.predicate = predicateFailsAfterSave;
    
            NSError *error = nil;
            NSArray *guestsNotCheckedInArray = [[self.managedObjectContext executeFetchRequest:request
                                                                             error:&error] mutableCopy];
            if (guestsNotCheckedInArray == nil) {
                NSLog(@"Error initializing GuestsNotCheckedIn %s: %@", __PRETTY_FUNCTION__, error.localizedDescription);
            }
    
            NSLog(@"GuestsNoCheckedIn (game: %p; context:%p): %lu", self.game, self.managedObjectContext, (long)guestsNotCheckedInArray.count);
    
        }
    
  4. 上述代码的输出:

      

    GuestsNoCheckedIn(游戏:0x10b54aeb0;上下文:0x10b526840):18

         

    通知:{       inserted =“{(\ n(实体:游戏; id:0x10b54af10    ;数据:   {\ n guests =(\ n); \ n})\ n)}“;       managedObjectContext =“”; }

         

    GuestsNoChecked In(游戏:0x10b54aeb0;上下文:0x10b526840):0

    预期产出:

    GuestNoCheckedIn ...:18次。

    我一直在研究三种理论:

    1. 这种关系在保存期间受到影响。
    2. 谓词中有一些错误导致它第二次失败。
    3. 获取对象时,他们的关系会被意外更改。
    4. 为了帮助测试这些理论,我已经:

      1. 从项目中删除了NSManagedObject子类。这向我保证,没有可能破坏关系的-awakeFrom *。
      2. 从模型中删除了所有其他实体。
      3. 已注册接收NSMAnagedObjectContextObjectsDidChangeNotification通知,因此我可以看到是否发生了一些我没想到的事情。
      4. 我比较了MOC和Game对象的地址,以确保我不会意外地创建另一个。
      5. 这让我觉得问题在于谓词,而不是保存。虽然我有点亏。

        有什么想法吗?

2 个答案:

答案 0 :(得分:2)

第一次获取请求的事实可能导致不同的行为 对已经在托管对象上下文中加载的对象执行, 第二个获取请求被转换为SQLite查询。

我不确定“value IN key”形式的Core Data谓词是否真的有效, 所以你可以用等价物替换谓词:

[NSPredicate predicateWithFormat:@"NOT (ANY games == %@)", self.game]

然而, 这里似乎是包含“NOT ANY”的Core Data获取请求的问题, 所以我会尝试以下一个:

[NSPredicate predicateWithFormat:@"SUBQUERY(games, $g, $g == %@).@count == 0",
     self.game]

答案 1 :(得分:1)

我认为您滥用IN关键字(使用games关系的名称而非实际收集)。 与IN关键字一起使用的集合必须是数组,集合或字典。在评估谓词时,games关系可能不会返回任何有效集合。

有效使用IN(根据Apple文档):

NSPredicate *inPredicate = [NSPredicate predicateWithFormat: @"attribute IN %@", aCollection];

在这种情况下,aCollection对象可以清楚地声明为有效集合(数组,集合,字典)。 文档(https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html#//apple_ref/doc/uid/TP40001795)没有提到我们可以使用关系名称而不是预定义的集合。虽然从技术上讲,评估“到多个”关系会产生一个集合,但在评估谓词时可能不会将其视为一个集合。

编写谓词的另一种方法可能是

NSPredicate *predicateFailsAfterSave = [NSPredicate predicateWithFormat:@"NOT (ANY games == %@)", self.game];