上篇中讲了 CoreData 里增删改查的使用,学到这里已经可以应对简单的数据存储需求了。但是当数据模型复杂起来时,例如你的模型类中除了要存储 CoreData 里支持的数据类型外,还有一些自定义的数据类型,这个时候只靠单一的模型结构就没办法来满足这种需求了,于是我们就需要使用关联表结构
多表关联,相信接触过 SQL 数据库的朋友都不陌生,就是指可以在一张表的数据中可以引用另外一张表里的数据
创建关联表
CoreData 里的多表关联,是通过 Entity 实体中的 Relationships 来实现的:

之前我们已经创建了Student实体,现在再创建两个实体,分别是School和Course,分别代表学校和课程


创建好表后,我们来建立它们之间的依赖关系,它们之间的关系是:Student中有一个字段studentSchool代表学生所在的学校,studentCourses代表学生所上的所有课程;School中有一个字段schoolStudents,代表学校里的所有学生,Course中有一个字段courseStudents,代表学这门课程的所有学生。可以看出这几个字段都有一定的关联性,所以我们可以通过Relationship来创建这些关联的字段

先看 studentSchool 字段,Destination 属性指的是和这个 字段建立关联的实体的名字,这里可以选择 DataModel 中的其他实体,也可以是当前的这个实体(好友关系中,就是和当前实体本身建立关联),这里我们选择 School 实体,另外我们还可以在右侧的 Data Model Inspector 面板中配置一些当前 relationship 的其他信息,Inverse 指的是在 School 实体中和 studentSchool 字段反向关联的字段,根据上面的描述,这里应该指定 schoolStudents;右边的 type 可以选择关联关系的类型,有 To One 和 To Many 两种选择,分别代表对一关系和对多关系,显然一个学生只能在一个学校里,所以这里选择 To One。同样的,来创建 studentCourses 字段,创建好之后是这样的:

现在我们来创建另外两个字段:
School实体中的schoolStudents:

Course实体中的courseStudents:

需要说明的是,这两个字段反向关联到 Student 实体对应的字段的类型都是一对多的,这里记得要正确设置。然后设置 Inverse 后,Student 实体里对应的 Inverse 属性也会自动设置。
DataModel 除了上面这种展示方式,还可以展示为关系图的形式,点击右下角的切换键就可以切换过去:

在这种模式下,各个实体之间的关系就一目了然了,单个箭头表示的是对一的关系,双箭头就表示对多的关系。从这里可以很方便地检查我们的表关系有没有设置正确。
删除规则:
关联表创建好后,还差一个比较重要的属性没有介绍,就是 Relationship 的删除规则:

删除规则(Delete Rule)规定了这条数据删除时,它所关联的数据该执行什么样的操作。这里有四种规则可以选择:
- No Action
- Nullify
- Cascade
- Deny
下面通过一个例子来说明这四个类型都有什么效果:
假如我们删除一名学生,
如果把 Delete Rule 设置成 No Action,它表示不做任何,这个时候学生所在的学校(School.schoolStudents)依然会以为这名学生还在这个学校里,同时课程记录里也会以为学习这门课程(Course.courseStudents)的所有学生们里,还有这位学生,当我们访问到这两个属性时,就会出现异常情况,所以不建议设置这个规则;
如果设置成 Nullify,对应的,班级信息里就会把这名学生除名,课程记录里也会把这名学生的记录删除掉;
如果设置成 Cascade,它表示级联操作,这个时候,会把这个学生关联的班级以及课程,一股脑的都删除掉,
如果 School 和 Course 里还关联着其他的表,而且也设置成 Cascade 的话,就还会删除下去;如果设置成 Deny,只有在学生关联的班级和课程都为 nil的情况下,这个学生才能被删除,否则,程序就会抛出异常。
所以这里,我们把 Student 的 studentSchool 和 studentCourses 的删除规则设置成 Nullify 是最合适的。
实体类生成
在有关联关系的实体里,对一关系是通过对应实体类的属性来表示的,对多属性则是实体类的集合,我们可以看一下 Xcode 自动生成的实体类代码(自动生成的代码默认在 /Users/userName/Library/Developer/Xcode/DerivedData/ProjectName/Build/Intermediates/ProjectName.build/Debug-iphonesimulator/ProjectName.build/DerivedSources/CoreDataGenerated/ProjectName/):
#import "Student+CoreDataClass.h"
NS_ASSUME_NONNULL_BEGIN
@interface Student (CoreDataProperties)
+ (NSFetchRequest<Student *> *)fetchRequest;
@property (nonatomic) int32_t age;
@property (nullable, nonatomic, copy) NSString *name;
@property (nullable, nonatomic, retain) NSSet<Course *> *studentCourses;
@property (nullable, nonatomic, retain) School *studentSchool;
@end
@interface Student (CoreDataGeneratedAccessors)
- (void)addStudentCoursesObject:(Course *)value;
- (void)removeStudentCoursesObject:(Course *)value;
- (void)addStudentCourses:(NSSet<Course *> *)values;
- (void)removeStudentCourses:(NSSet<Course *> *)values;
@end
NS_ASSUME_NONNULL_END
对关联表进行增删改查和单表的增删改查没多大区别,只需要配置一下关联的属性即可:
import "Student+CoreDataProperties.h"
import "School+CoreDataProperties.h"
import "Course+CoreDataProperties.h"
这三个头文件得导入
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
School *school = [[School alloc] initWithContext:self.context];
school.schoolName = @"清华大学";
student.studentSchool = school;
Course *english = [[Course alloc] initWithContext:self.context];
Course *math = [[Course alloc] initWithContext:self.context];
[student addStudentCoursesObject:english];
[student addStudentCourses:[NSSet setWithObjects:english, math, nil]];
[self.context save:nil];
调用 save 后,student 和它关联的 school、math、english 都会保存到数据库中
网友评论