如何管理和使用“多对多”核心数据关系?

27
我正在创建一个应用程序,并尝试使用Core Data,因为它似乎是创建数据存储系统的Objective-C认可方式。我有一个使用“多对多”关系的用例,就像您通常在标准SQL系统中看到的那样。我知道Objective-C不是数据库,工作方式也不同。我已经阅读了这里的文档:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW10和其他几个地方。但是,我仍然遇到问题。有人能解释一下如果您需要使用SQL交叉引用表的用例会怎么做吗?例如:
经理 | 员工
经理可能有几个员工,但员工也可能有几个经理。在SQL中,我会创建一个交叉引用表,然后使用它。
例如: http://www.tomjewett.com/dbdesign/dbdesign.php?page=manymany.php 有人能解释一下如何在Core Data中实现这个功能吗?
根据Core Data文档,他们说:
“您可以使用两个一对多关系定义多对多关系。第一个一对多关系从第一个实体到第二个实体。第二个一对多关系从第二个实体到第一个实体。然后,您将每个关系设置为另一个关系的反向关系。(如果您具有数据库管理背景,并且这使您感到担忧,请不用担心:如果使用SQLite存储,则Core Data会自动为您创建中间联接表。)”
但是,除了“不用担心”之外,我不知道这怎么可能行得通?

这是 https://dev59.com/ZEnSa4cB1Zd3GeqPN255 的重复吗? - Scott Harwell
不需要,因为那个人引用了一个连接表。根据我在文档中所读到的,您不需要创建连接表?此外,我认为他正在询问获取数据的问题。 - Darkenor
5个回答

66

Peter: "我有九个上司,Bob。" Bob:"请再说一遍?" Peter:"九个。"

把你的思维从数据库中抽离出来。不要问如何访问交叉引用表,而要问如何查找经理的员工或员工的经理。

如果你使用NSManagedObject的自定义子类,你可以声明文档化的属性。然后你就可以简单地说:

NSSet *mgrsEmployees = mgr.employees;
NSSet *employeesMgrs = employee.managers;

那样做不会给你所有员工和所有经理,只会给你该经理的所有员工和该员工的所有经理。要更改关系:

[mgr addEmployeesObject:newEmployee];
[newEmployee addManagersObject:mgr]; // not necessary, automatic if you define inverse

[mgr removeEmployeesObject:transferredEmployee];
[transferredEmployee removeManagersObject:mgr]; // not necessary
[newMgr addEmployeesObject:transferredEmployee];
[transferredEmployee addManagersObject:newMgr]; // not necessary

对于每一对语句,您只需要执行其中的一个,它会隐式地执行另一个操作,前提是您已将管理者和员工定义为互相反转。

如果您不使用自定义子类,则访问略显冗长。

NSSet *mgrsEmployees = [mgr valueForKey:@"employees"];
NSSet *employeesMgrs = [employee valueForKey:@"managers"];

NSMutableSet *changeMgrsEmployees = [mgr mutableSetValueForKey:@"employees"];
[changeMgrsEmployees addObject:newEmployee];
// not necessary
NSMutableSet *changeEmployeesMgrs = [employee mutableSetValueForKey:@"managers"];
[changeEmployeesMgrs addObject:mgr];

7
这是我在Stack Overflow上看到的最好的答案之一。非常感谢!我实际上眼泪都要出来了,因为我之前非常困惑。 - Darkenor
在上下文被保存之前,我如何获取所有员工? - raw3d
无论上下文是否保存,通过代码访问员工的结果都是相同的。只有在停止和启动程序或丢弃上下文等情况下,才会受到保存的影响。 - morningstar
我想从一个实体中显示4列,从另一个实体中显示2列,那么我该怎么做呢? - Lydia
假设有两个实体具有多对多的关系(A和B)。假设我为A1记录添加了B1和B2,为A2记录添加了B1和B3。对于B1来说,我会自动获得(A1和A2)吗? - NSPratik

4
如果您的问题是“如何在Core Data中完成此操作”,答案正如您所描述的那样。在每个实体中创建一个多对多关系,并将每个关系定义为另一个关系的逆。
如果您的问题是“这可能如何工作”,您可能会发现查看从模型创建的sqlite数据库有用,以了解它如何设置事物。CoreData非常擅长使您不必关心细节,但如果您坚持要理解细节,那么您可以查看数据以了解其正在做什么。
一旦您完成了这个步骤,您需要根据需要遍历关系以获取所需的属性。您可以使用NSManagedObject实例在代码中执行此操作。例如:
NSManagedObject *manager = // get a reference to a manager here
NSSet *employees = [manager valueForKey:@"employees"];
for (NSManagedObject *employee in employees) {
    NSString *employeeName = [employee valueForKey:@"name"];
    // Do whatever else you want here
}

这个示例也可以使用直接属性访问而不是KVO语法来简化,但你明白了。
如果你想要在查询中更直接地遍历这种多对多关系,则可以使用谓词来获取所需内容。请参考Apple的谓词编程指南以获取可放入谓词中的示例。

知道它的工作原理是好的,但我真正想知道的是如何访问交叉引用表?我的意思是,你还能用什么方法将两个实体实际映射到一起呢?一个实体的属性并不具备另一个实体的每个属性。 - Darkenor
我稍微更新了回复,包括使用代码访问交叉引用数据的示例。如果您正在寻找特定类型查询的示例,请澄清您要查找什么,我们可能会提供一个谓词,以实现您想要的结果。 - Tim Dean

2

假设你的数据库中有两个实体,员工和经理。它们在代码中看起来像这样:

@interface Employee :  NSManagedObject  
{

}

@property (nonatomic, retain) NSNumber * id;
@property (nonatomic, retain) NSSet* managers;

@interface Manager :  NSManagedObject  
{

}

@property (nonatomic, retain) NSNumber * id;
@property (nonatomic, retain) NSSet* employees;

假设你想检索所有经理编号为1的员工,你只需从数据库中获取经理对象:

NSNumber *managerID = [NSNumber numberWithInt:1];
NSEntityDescription * entityDescription = [NSEntityDescription entityForName:@"Manager"                                                       inManagedObjectContext:managedObjectContext]; 
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];  
[request setEntity:entityDescription];  
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %d", @"id", managerID];   
[request setPredicate:predicate];
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
Manager *manager = [array objectAtIndex:0];

然后您将在employees属性中拥有所有员工:

NSSet *allHisEmployees = manager.employees;

对于给定的员工获取经理,这完全是一样的。

我希望这能够澄清事情。


2
简单的答案是在设置xCode中的核心数据时,从两端将关系标记为一对多。本质上,这会创建一个多对多的关系。这在苹果文档中有提到,但我找不到URL了。

但是,嗯 - 我怎么知道哪些员工有经理,哪些经理有员工呢?我的意思是,否则它们只会是一个很长的列表? - Darkenor
你可以使用 employee.managers 获取该员工的所有经理。对于一个经理,你只需使用 manager.employees 就可以获取该经理所管理的所有员工实体。(“employee”和“manager”都是指代一个人的特定实体实例。) - Kendall Helmstetter Gelner

0

可能你卡住的地方是如何从抽象的数据模型领域转换为一组在代码中使用的对象。如果是这样,可以考虑使用mogenerator:

http://rentzsch.github.com/mogenerator/

这是一个命令行实用程序(不确定 XCode 集成的效果如何),您可以将其指向一个模型,然后它会给您一组可用的数据对象。除了实体名称之外,每个实体都需要设置一个对象名称。

生成的数据对象具有像 employee.managers 这样的调用,这将为员工提供一组 Manager 实体,反之亦然。


1
@MohsinKhubaibAhmed - 是的,Mogenerator仍然可用,我最近参与的项目中使用它来生成核心数据对象。 - Kendall Helmstetter Gelner
好的,我会仔细查看它。你能推荐一些资源让我开始学习吗?比如生动形象的演示之类的。 - Mohsin Khubaib Ahmed
没事了,我找到了:https://medium.com/@yzhong.cs/getting-started-guild-with-mogenerator-in-xcode-d715944a1084 - Mohsin Khubaib Ahmed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接