CoreData 中的自引用数据对象
Posted
技术标签:
【中文标题】CoreData 中的自引用数据对象【英文标题】:Self-Referencing Data Object in CoreData 【发布时间】:2013-12-07 21:51:49 【问题描述】:我正在从一生的 SQL 转换到 CoreData,这并非没有一些小问题。此刻让我兴奋的事情是创建一个自引用对象。请允许我解释一下。
使用 SQL 术语,我有一个包含流程中的步骤的表。为了简单起见,我会说该表包含两条信息 - 步骤的名称和紧随其后的步骤(可能是也可能不是表中的下一条记录)。流程中的所有步骤都存储在一个表中。每一步总会有一个步骤在它之后。但并不是所有的步骤都有前面的步骤。
换句话说,它应该看起来像这样:
(来源:justinwhitney.com)
在 SQL 世界中,我会创建一个包含标识字段、名称和引用其自身标识字段的外键(我猜那将是国内键?)的表,因此:
(来源:justinwhitney.com)
但是,对于关系,没有身份字段之类的东西。也没有我可以创建的选择查询来提取我需要的信息。
那么创建一个做同样事情的实体的最佳方法是什么?我尝试创建一种与自身相反的关系,结果证明这是一场难以调试的灾难。还有什么其他选择?
谢谢!
【问题讨论】:
【参考方案1】:创建从实体到自身的关系“nextStep”。然后你可以 做类似的事情
// Create first thing:
Thing *thingA = [NSEntityDescription insertNewObjectForEntityForName:@"Thing" inManagedObjectContext:context];
thingA.name = @"...";
// Create second thing:
Thing *thingB = [NSEntityDescription insertNewObjectForEntityForName:@"Thing" inManagedObjectContext:context];
thingB.name = @"...";
// Establish the relationship between these two objects:
thingA.nextStep = thingB;
“nextStep”也应该具有“反向关系”。由于两个或多个对象 可以有相同的后继(如你的情况,“C”和“D”都指向“E”), 反向关系将是“对多”关系,可以称为 "previousSteps" 或类似的。
在 Core Date 模型编辑器中,如下所示:
【讨论】:
【参考方案2】:啊!你提供了重要的线索,马丁。
我尝试了代码示例,但效果不佳。它最终创建了所有内容的副本,因为 thingA 和 thingB 都插入了表中。然而,这张图实际上给了我我认为可能是关键的东西。以前我曾尝试将 nextStep 指定为它自己的逆关系,这简直是疯了。但只是添加 previousSteps 并将其设置为 Many 而 nextStep 设置为 One 似乎导致了解决方案。
这是我最终为关系创建的内容:
(来源:justinwhitney.com)
这是我用来填充 Steps 实体的 plist(打算在首次使用或重置数据库时运行):
(来源:justinwhitney.com)
现在这里是实体填充例程。这就是昨天让我绊倒的原因:
- (IBAction)populateSteps:(UIButton *)sender
NSString *responseString;
BOOL isStepsPopulated = [[UserPrefs loadUserData:@"didPopulateSteps"] boolValue];
if (!isStepsPopulated)
NSDictionary *stepDict = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"DefaultSteps" ofType:@"plist"]];
NSArray *stepArray = [stepDict objectForKey:@"Steps"];
//1
__block NSMutableDictionary *stepObjectDict = [[NSMutableDictionary alloc] initWithCapacity:[stepArray count]];
//2
[stepArray enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop)
//3
Steps *thisStep = [NSEntityDescription insertNewObjectForEntityForName:@"Steps" inManagedObjectContext:self.managedContext];
thisStep.stepName = [dict objectForKey:@"StepName"];
//4
[stepObjectDict setObject:thisStep forKey:thisStep.stepName];
];
//5
[stepArray enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop)
Steps *thisStep = [stepObjectDict objectForKey:[dict objectForKey:@"StepName"]];
Steps *nextStep = [stepObjectDict objectForKey:[dict objectForKey:@"NextStep"]];
thisStep.nextStep = nextStep;
];
NSError *error = nil;
[self.managedContext save:&error];
if (error)
responseString = [NSString stringWithFormat:@"Error populating Steps: %@", error.description];
else
responseString = @"Steps have been populated.";
[UserPrefs saveUserData:[NSNumber numberWithBool:TRUE] forKey:@"didPopulateSteps"];
else
responseString = @"Steps already populated.";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Populate Steps" message:responseString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
这是关键。在 (1) 创建一个 NSMutableDictionary 来保存迭代的结果,使用 stepName 作为 Key,以便以后可以引用它。
在 (2) 枚举 plist 内容时,(3) 像您一样创建第一个 NSEntityDescription。但不是创建第二个,而是 (4) 将其添加到字典中。
一旦完成初始枚举,(5) 再返回第二次。但是这一次,通过引用原始对象本身来创建关系。有意义吗?
之后,正常保存上下文。在这种情况下,结果是 5 条记录,每条记录都引用同一实体中的另一条记录,没有冲突或重复。
(来源:justinwhitney.com)
现在重要的问题是:如何将其标记为已回答?谁得到复选标记?
【讨论】:
这完全有道理。实际上,我的代码示例主要是为了展示如何在两个对象之间建立关系(因为我不知道 如何 填充您的数据)。 - 接受哪个答案完全取决于您。以上是关于CoreData 中的自引用数据对象的主要内容,如果未能解决你的问题,请参考以下文章
Cocoa:CoreData - ManagedObjectContext 中的多个实体
iOS中的自定义排序,希望在Coredata的顶部显示默认(所有组)标题
NHibernate 中的自引用实体给对象引用一个未保存的瞬态实例异常
CoreData + mogenerator - 如何防止中间数据模型中的`setValue(forKey :)`引用最终数据模型中实体的'人类'类?