获取一对多的核心数据关系第一次返回正确的对象,但其他时候返回空集

Posted

技术标签:

【中文标题】获取一对多的核心数据关系第一次返回正确的对象,但其他时候返回空集【英文标题】:Fetching a one-to-many core data relationship returns correct objects the first time but empty set all other times 【发布时间】:2012-05-09 18:47:34 【问题描述】:

我有一个对象,锻炼,它与一个对象,锻炼有一对多的关系。

模型图:http://i.imgur.com/q1Mfq.png

当我创建一个锻炼对象时,我通过循环添加三个锻炼对象

[self addExercisesObject:exercise]

然后保存我的托管对象上下文。然后,从用于显示锻炼的控制器中,我可以成功获取锻炼及其锻炼(使用获取请求),如调试器中的输出所示:

Printing description of self->_savedWorkout:
<Workout: 0x6c5a990> (entity: Workout; id: 0x6e46e00 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Workout/p1> ; data: 
bodyweight = nil;
date = "2012-05-09 16:59:43 +0000";
exercises =     (
    "0x6e3c870 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p3>",
    "0x6e3eaf0 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p2>",
    "0x6e36820 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p1>"
);
isCompleted = 0;
workoutId = 1;
workoutPlan = "0x6e6c980 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/WorkoutPlan/p1>";
)

到目前为止一切顺利。但是,如果我在模拟器中关闭我的应用程序并重新启动它并在同一视图中执行相同的获取请求,则锻炼看起来像这样:

Printing description of self->_savedWorkout:
<Workout: 0x6ea8ff0> (entity: Workout; id: 0x6e8f9e0 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Workout/p1> ; data: 
bodyweight = nil;
date = "2012-05-09 16:59:43 +0000";
exercises =     (
);
isCompleted = 0;
workoutId = 1;
workoutPlan = "0x6c8a130 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/WorkoutPlan/p1>";
)

它似乎获取了相同的锻炼对象,但现在锻炼是一个空集。实际上,在 fetch 请求之后,练习首先看起来像这样:

exercises = "<relationship fault: 0x8a93100 'exercises'>";

但是一旦我这样做了:

for (Exercise *exercise in self.savedWorkout.exercises)

self.savedWorkout.exercises 解析为一个空集。我不会在我的应用程序的任何部分编辑锻炼。

我的获取请求是通过我的 Workout 类中的此方法发出的:

- (Workout *)getLatestWorkout

  self.model = [[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];

  NSFetchRequest *fetchRequest = [self.model fetchRequestTemplateForName:@"getLatestWorkout"];

  NSError *error = nil;
  NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
  if ([results count] == 1) 
    return [results objectAtIndex:0];
  
  return nil;

我使用 Xcode 的 GUI 工具制作了获取请求模板。它获取 isCompleted == 0 的所有 Workout 对象。您可以看到它每次都获取相同的对象,因为在两个调试器输出中,锻炼的 x-coredata 路径是相同的。

更新:我检查了我的 SQLite 数据库。锻炼表中有一项锻炼,锻炼表中有三项锻炼。

有什么想法吗?

编辑:创建下面发布的对象的代码



- (void)storeUserSettings

  // get the file path if it exists
  NSString *path = [[NSBundle mainBundle] pathForResource:@"userSettings" ofType:@"plist"];

  // create it if it doesn't
  if (path == nil) 
    path = [NSString stringWithFormat:@"%@%@", 
            [[NSBundle mainBundle] bundlePath], @"/userSettings.plist"];
  

  // and write the new settings to file
  [self.userSettings writeToFile:path atomically:YES];

  // load managed object context
  [self loadMOC];

  WorkoutPlan *currentPlan = [[WorkoutPlan alloc] getActiveWorkoutPlan];

  [currentPlan setManagedObjectContext:self.managedObjectContext];

  // if user has no plan or is changing plans, create new plan and first workout
  if (currentPlan == nil || 
      ([self.userSettings valueForKey:@"plan"] != currentPlan.planId)) 

    // create a workoutPlan object
    WorkoutPlan *workoutPlan = [[WorkoutPlan alloc] initWithEntity:
                                [NSEntityDescription entityForName:@"WorkoutPlan" 
                                            inManagedObjectContext:self.managedObjectContext] 
                                    insertIntoManagedObjectContext:self.managedObjectContext];

    // set attributes to values from userSettings and save object
    [workoutPlan createWorkoutPlanWithId:[self.userSettings valueForKey:@"plan"] 
                                schedule:[self.userSettings valueForKey:@"schedule"]
                             dateStarted:[self.userSettings valueForKey:@"nextDate"]];
  
  // if user is just changing schedule, update schedule of current plan
  else if (![currentPlan.schedule isEqualToString:[self.userSettings valueForKey:@"schedule"]]) 
    [currentPlan setSchedule:[self.userSettings valueForKey:@"schedule"]];

    [currentPlan saveMOC];
  


- (void)loadMOC

  AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
  self.managedObjectContext = delegate.managedObjectContext;
  self.model = [[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];


- (void)createWorkoutPlanWithId:(NSNumber *)planId schedule:(NSString *)schedule 
                    dateStarted:(NSDate *)dateStarted
 
  [self deactivateCurrentPlan];

  // set workout plan attributes
  [self setPlanId:planId];
  [self setIsActive:[NSNumber numberWithBool:YES]];
  [self setSchedule:schedule];
  [self setDateStarted:dateStarted]; 

  // create first workout and add to workout plan

  Workout *firstWorkout = [NSEntityDescription
                          insertNewObjectForEntityForName:@"Workout"
                          inManagedObjectContext:self.managedObjectContext];

  [firstWorkout setManagedObjectContext:self.managedObjectContext];

  [firstWorkout createFirstWorkoutForPlan:self onDate:dateStarted];

  [self addWorkoutsObject:firstWorkout];

  [self saveMOC];


- (void)createFirstWorkoutForPlan:(WorkoutPlan *)plan onDate:(NSDate *)startDate

  // set workout attributes
  [self setDate:startDate];
  [self setIsCompleted:[NSNumber numberWithBool:NO]];
  [self setWorkoutId:[NSNumber numberWithInt:1]];

  NSArray *exerciseList = [self getExercisesForWorkout:self inPlan:plan];

  // iterate over exercises in spec and create them
  for (NSDictionary *exerciseSpec in exerciseList) 
  
    // create a exercise MO
    Exercise *exercise = [NSEntityDescription
                          insertNewObjectForEntityForName:@"Exercise"
                          inManagedObjectContext:[plan managedObjectContext]];

    [exercise setManagedObjectContext:[plan managedObjectContext]];
    [exercise createExerciseForWorkout:self withSpec:exerciseSpec];

    // add exercise to workout object
    [self addExercisesObject:exercise];
  



- (void)createExerciseForWorkout:(Workout *)workout withSpec:exerciseSpec

  // set exercise attributes
  self.exerciseId = [exerciseSpec valueForKey:@"id"];
  self.isPersonalRecord = [NSNumber numberWithBool:NO];

  NSArray *sets = [exerciseSpec valueForKey:@"sets"];
  int i = 1;
  for (NSNumber *setReps in sets)
  
    // create a set MO
    Set *set = [NSEntityDescription
                          insertNewObjectForEntityForName:@"Set"
                          inManagedObjectContext:[workout managedObjectContext]];

    [set setManagedObjectContext:[workout managedObjectContext]];

    // set set attributes
    set.order = [NSNumber numberWithInt:i];
    set.repetitions = setReps;
    set.weight = [exerciseSpec valueForKey:@"default_weight"]; 

    // add set to exercise object
    [self addSetsObject:set];
    i++;
  

【问题讨论】:

尝试检查核心数据 SQLite 文件,看看练习是否真的被保存了。这将是一个很好的起点,以便了解他们是否真的得救了。 Firefox 有一些扩展允许这样做。 我刚刚用 SQLite 数据库浏览器再次查看,练习表中确实保存了三个练习。 你如何构造你的获取请求? 一种检查方法是获取练习并查看它们是否具有有效的锻炼关系。 @omz 添加了编辑中详细说明的获取请求。 【参考方案1】:

我遇到了类似的问题。应用程序运行时父子关系有效,但重新启动后仅检索到最新的子记录。

我是这样添加孩子的:

创建子记录 设置孩子的parent属性,设置孩子的other 属性 使用父级的 add 方法将子级添加到父级

如果我这样做,我发现它是固定的:

创建子记录 使用父级的 add 方法将子级添加到父级 设置孩子的parent属性,设置孩子的other 属性

【讨论】:

【参考方案2】:

核心数据很复杂。可能有很多事情需要检查,任何一件事情都可能导致问题。

您使用了多少个 MOC?你怎么存钱?还有很多问题...

我建议在启动应用程序时为参数打开 EditScheme 中的 SQL 调试标志 (-com.apple.CoreData.SQLDebug 1)。

运行您的代码,看看实际发生了什么。

关系在获取时解决为错误,除非您在获取请求中覆盖它。

如果您在父/子关系中使用多个 MOC,则从子级到父级的保存只是将数据放入父级,并没有真正保存它。如果使用 UIManagedDocument,这是一组完全不同的问题...

我希望这听起来不刺耳。准备好为 Core Data 问题提供大量信息,而不是“这不是保存,这是一些调试输出。”

基本上,CoreData 的工作方式取决于堆栈的创建方式、是否使用 UIManagedDocument、多线程、如何创建对象、如何保存它们、获取请求的选项以及更多其他事情。

实际上并没有那么复杂,但是根据使用方式的不同,有很多自定义和特殊情况。

编辑

发布创建对象/关系的代码。

另外,尝试使用手动提取请求而不是模板进行提取。当您查看数据库中的数据时,您是否看到适当设置的关系的外键?

在启用调试的情况下再次运行它,以查看 SQL 正在做什么。这比你自己的调试输出更有价值。

【讨论】:

感谢您的提示。我相信数据正在保存。保存 MOC 后,我的 sqlite 浏览器在我的数据库中显示一个锻炼行和三个锻炼行。我只使用一个 MOC。我不使用 UIManagedDocument。你可能是对的,虽然关系没有正确创建。我创建了一个锻炼对象并将其加载到上下文中。然后我创建练习,将练习添加到锻炼对象。我没有将锻炼的锻炼属性设置为锻炼,因为我相信这对于反向关系会自动发生。然后我保存我的 MOC。这听起来正确吗? 检查了我表中的每个外键。它们都映射到相应表中的正确行。这可能意味着我的关系创建正确,但我的获取请求不是。我将尝试发出手动提取请求并尽快在此处发布。 创建发布对象的代码。【参考方案3】:

我遇到了同样的问题,但我的模型非常复杂。如果实体和关系不存在,我的应用会在启动时创建它们。如果它们被创建并且我没有退出应用程序,我可以获取具有一对多关系的实体并查看相关对象的正确计数。如果我退出我的应用程序并重新启动它(它现在知道它不必创建默认数据集),那么关系将返回一个空集。我想不通。

编辑:我发现我的问题与有序集合关系有关。我必须使用 Category 来创建解决方法(在堆栈溢出时发现)将新条目插入到有序集中。所以我猜这与它有关。

【讨论】:

以上是关于获取一对多的核心数据关系第一次返回正确的对象,但其他时候返回空集的主要内容,如果未能解决你的问题,请参考以下文章

一对多关系核心数据:在prepareForSegue中获取:

核心数据:以一对多的关系获取实体的获取请求

核心数据关系无返回值

iOS:如何获取核心数据中数量最多的对象?

对多关系的简单核心数据获取

获取核心数据对象,其中一对多关系包含集合中的所有对象