前面几篇文章中讲的所有内容,都是在同一个模型版本上进行操作的。但在真实开发中,基本上不会一直停留在一个版本上,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模的重构;所以数据的迁移就显得尤为重要了。
CoreData
中,数据迁移本质就是把旧的 SQLite 数据库里的内容,复制到新的 SQLite
数据库里去,让新的数据库作为默认的数据存储。伴随着模型版本的变化,新旧两个数据库的实体结构当然也是不同的。这就是说在迁移过程中必须知道新旧两个数据库的模型对应关系,旧数据库里的数据该怎么复制到新的数据库中。这在
CoreData 中是由 MappingModel 映射模型来决定的。我们所需要做的就是创建 MappingModel
文件,指定好实体不同版本间的映射,CoreData 就会自动帮我们完成数据迁移。当然如果模型版本的变化比较小,CoreData
是可以自动推断出映射模型的。下面就来详细的介绍一下 CoreData 里常用的几种迁移。

创建模型版本

在介绍数据迁移之前,先来看如何创建新的模型版本,在 Xcode 里模型是通过 .xcdatamodeld
文件来创建的,实际上这个文件就是一个包,里面可以包含不同的模型版本。选中这个文件,然后点击 Editor->Add Model
Version... 就可以添加一个新的模型版本。

add-model-version-w400

然后会弹出下面这个对话框,默认的新的模型会在原来的基础上增加一个数字,来标识不同的模型版本。这个数字也是可以更改的,你可以按照自己的喜好更改成 v2 或者其他的。

version-name-w600

点击 finish 后就会看到现在的
LearnCoreData.xcdatamodeld文件可以展开了,里面包含了所有的模型版本文件,它们是 xcdatamodel
格式的。在右侧的 File Inspector 面板中可以指定当前的模型版本,然后程序打包后就会把选中的模型版本作为当前的默认版本。

model-version-w300

自动推断映射模型

上面说到对于一些较小的变化,CoreData 是可以自动推断映射模型的,从而帮助我们自动地完成数据迁移。针对下面这些改动,CoreData 都可以自动的进行推断:

  • 添加一个属性
  • 移除一个属性
  • 非空的属性变成可以为空的
  • 可以为空的属性变成非空属性并设置一个默认值
  • 重命名实体或者属性(需要设置 renaming identifier)
  • 添加/删除 RelationShip
  • 重命名 RelationShip(需要设置 renaming identifier)
  • 把一个 RelationShip 从 对一改成对多,或者把非排序的改成排序的。(反过来也是可以的)

上面说到的 renaming identifier 可以在 Model Inspector 进行设置,对不同版本的对应实体/属性设置相同的 Renaming ID,CoreData 就可以自动推断出对应的映射模型。

renaming-identifier-w600

除此之外,在向 persistentStoreCoordinator 调用 addPersistentStoreWithType:configuration:URL:options:error: 添加 persistentStore时,需要将 optionsNSMigratePersistentStoresAutomaticallyOptionNSInferMappingModelAutomaticallyOption 两个 key 设置为 YES,CoreData 才会自动推断。

下面我们来看一下,怎么使用自动推断。这是初始版本的 StudentEntity 实体的结构:

StudentEntity-1-w600

下面我们再创建一个 Model Version,把原来的 StudentEntity、ClassEntity、CourseEntity 的
EntityName 分别修改成 Student、Clazz、Course;Student 里面的字段修改成 name、id 和
age,另外再添加一个 BOOL 字段 sex,表示性别,默认值设置为 YES。

StudentEntity-2-w600

然后为两个版本中修改过的实体名字和属性字段名字设置相同的 renaming identifier。以 Student 的 name 字段为例,旧版的模型中:

studentName-RenamingID-w600

然后新版本的模型中:

new-name-w600

修改好后,暂时我们先不切换到新版本的模型中,先用旧的数据库生成一些测试数据,然后在沙盒的 Library/Application
Support/ 目录里复制出里边的三个文件,然后用 SQLite 工具打开 .sqlite 的数据库文件查看数据库的的结构,和刚存进去的内容。

sqlite-w600

这是打开后的 StudentEntity 表,里面随机插入了 300 条数据,注意到现在由我们创建的几个字段分别是 ZSTUDENTID、ZSTUDENTCLASS、ZSTUDENTNAME。

StudentEntity-v1-w600

现在我们把数据库切换到新版中,然后再运行一次程序,重新打开新生成的数据库文件,就会看到新版的数据库的结构:

StudentEntity-v2-w600

现在 StudentEntity 已经变成了 Student,每个字段也都变成了新的字段名,而且里面也多了我们添加的 sex 字段。这就说明 CoreData 的自动推断成功了。

自定义映射模型

大多数情况下自动推断就能帮我们完成数据的迁移,但当数据的变化更复杂时,例如如果我们把 Student
里的一个字段提取出来放到一个新的字段中去。就得靠我们手动创建 mapping model 了。例如我们现在想把上面 Clazz 表删除,原来的
Student 中的 clazz 字段用 clazzName 字段来代替。那么这种情况下就需要手动来创建 mapping model 了。
在这之前我们先用旧版的数据模型插入一些示例的数据,这是插入的 Student 数据:

Student-data-w600

Clazz 数据:

Clazz-data-w600

Course 数据:

Course-data-w600

因为 Course 和 Student 是多对多的关系,所以还会有一张关联表:

SCoursesStudents-data-w600

这是插入示例数据的代码:

- (void)insertManyStudents {
NSSet *science = [self scienceCourses];
NSSet *art = [self artCourses];
Clazz *clazz1 = [[Clazz alloc] initWithContext:self.persistentContainer.viewContext];
clazz1.clazzName = @"文科一班";
clazz1.classId = 1; Clazz *clazz2 = [[Clazz alloc] initWithContext:self.persistentContainer.viewContext];
clazz2.clazzName = @"理科一班";
clazz2.classId = 2;
for (NSUInteger i = 0; i < 300; i++) {
NSString *name = [NSString stringWithFormat:@"student-%u", arc4random_uniform(100000)];
int16_t age = (int16_t)arc4random_uniform(10) + 10;
int16_t stuId = (int16_t)arc4random_uniform(INT16_MAX);
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.persistentContainer.viewContext];
student.name = name;
student.age = age;
student.id = stuId;
if (i % 2 == 0) {
student.clazz = clazz1;
student.courses = art;
} else {
student.clazz = clazz2;
student.courses = science;
} }
NSError *error; [self.persistentContainer.viewContext save:&error];
} - (NSSet<Course *> *)scienceCourses { Course *physics = [[Course alloc] initWithContext:self.persistentContainer.viewContext];
physics.courseName = @"物理";
physics.courseId = 1;
physics.courseChapterCount = 5; Course *chemistry = [[Course alloc] initWithContext:self.persistentContainer.viewContext];
chemistry.courseName = @"化学";
chemistry.courseId = 2;
chemistry.courseChapterCount = 9; Course *biology = [[Course alloc] initWithContext:self.persistentContainer.viewContext];
biology.courseName = @"生物";
biology.courseId = 3;
biology.courseChapterCount = 10; NSSet *courses = [NSSet setWithObjects:physics, chemistry, biology, nil];
return courses;
} - (NSSet<Course *> *)artCourses {
Course *chinese = [[Course alloc] initWithContext:self.persistentContainer.viewContext];
chinese.courseName = @"语文";
chinese.courseId = 4;
chinese.courseChapterCount = 12; Course *history = [[Course alloc] initWithContext:self.persistentContainer.viewContext];
history.courseName = @"历史";
history.courseId = 5;
history.courseChapterCount = 19; Course *geography = [[Course alloc] initWithContext:self.persistentContainer.viewContext];
geography.courseName = @"地理";
geography.courseId = 6;
geography.courseChapterCount = 21; return [NSSet setWithObjects:chinese, geography, history, nil];
}

然后我们再来看一下 新创建的 v3 版本的数据模型的结构:
Student 表

Student-table-w600

Course 表

Course-table-w600

这一次我们不再创建 Clazz 表了,因为它要被 Student 表里的 clazzName 字段代替。

接下来创建 Mapping Model 文件

Mapping-Model-w600

创建过程中需要选择 Source data model 和 Destination data model,也就是迁移的旧版和新版数据模型版本,分别选择 v2 和 v3 版本:

Source-data-model-w600

Target-data-model-w600

最后保存的文件名建议按一定的规则来命名,后期也方便查找:

Save-mapping-model-w600

然后我们来认识一下 mapping model 的用法,创建好后,mapping model 还是会自动推断出大多数的字段映射,例如 Student 表中除新添加的 clazzName 字段外,其他的都可以正确的推断出来;

StudentToStudent-mapping-w600

当然,如果字段名修改过的话,同样是不能推断出来的,如 Course 表的字段:

CourseToCourse-mapping-w600

另外每个 Entity Mapping 的名字的命名规则是以 SourceEntityNameToDestinationEntityName 来命名的,这个可以在右侧的面板中修改:

Entity-Mapping-name-w600

下面来介绍 mapping model 中会用到的几个对象:

  • $source - 对应着 NSMigrationSourceObjectKey,可以理解为 Source Model 的一个实体对象
  • $manager - 对应着 NSMigrationManagerKey,它代表的是 NSMigrationManager 对象,正是这个对象在迁移过程中发挥着作用,它管理着源对象和目标对象之间的关联

除了这两个,还有几个不常用的:

  • $destination -- NSMigrationDestinationObjectKey
  • $entityMapping -- NSMigrationEntityMappingKey
  • $propertyMapping -- NSMigrationPropertyMappingKey
  • $entityPolicy -- NSMigrationEntityPolicyKey

在 mapping model 中可以通过 \$ 加对应的名字,直接访问这几个对象。例如上面图中 \$source.name 就代表源对象的 name 属性。同样的我们就可以把其他未推断出来的填上:

-w600

-w600

然后再来看 Relationship Mapping 映射:

对于这种关联到外部表的字段,相对于普通字段会复杂一些,我们需要通过右侧的面板来进行配置,Name 代表
RelationShip 的字段名;Key Path 代表这个字段对应的源对象上的字段,对于 courses 来说就是
$source.courses;然后是 Mapping Name,它代表这个 RelationShip 所关联的外部表的 Entity
Mapping,对于 courses 来说就是 Course 的 Entity Mapping 也就是
CourseToCourse。配置好这些后,Xcode 会生成一段长长的 Value Expression 表达式:

FUNCTION($manager, "destinationInstancesForEntityMappingNamed:sourceInstances:" , "CourseToCourse", $source.courses)

意思就是调用 $manager 对象的 destinationInstancesForEntityMappingNamed:sourceInstances: 方法 CourseToCourse\$source.courses 分别是两个传入参数。 它会根据 CourseToCourse 的映射规则生成$source.courses 的目标对象。
同样的,我们可以据此来配置 Course 里的 students 关系:

-w600

所有字段都配置完后,就可以把 模型版本切换都 v3 然后运行程序。程序在运行时发现当前的 v3 版本数据模型和本地存储的 v2
数据库版本不一致,就会自动从 bundle 里寻找对应 v2 到 v3 的 Mapping Model,依据自定义的 Mapping
Model,数据就会自动迁移完成。
下面来看一下迁移完成的 v3 版本数据库。
Student 表:

Student-table-v3-w600

Course 表:

Course-table-v3-w600

自动生成的 Course 和 Student 之间的关联表:

Students-table-v3-w600

可以看到 Student 表中的 clazz 字段已经被 clazzName 替换了。同时其他的数据也都没有丢失。

CoreData 从入门到精通(六)模型版本和数据迁移的更多相关文章

  1. CoreData 从入门到精通(五)CoreData 和 TableView 结合

    我们知道 CoreData 里存储的是具有相同结构的一系列数据的集合,TableView 正好是用列表来展示一系列具有相同结构的数据集合的.所以,要是 CoreData 和 TableView 能结合 ...

  2. 【转载】salesforce 零基础开发入门学习(六)简单的数据增删改查页面的构建

    salesforce 零基础开发入门学习(六)简单的数据增删改查页面的构建   VisualForce封装了很多的标签用来进行页面设计,本篇主要讲述简单的页面增删改查.使用的内容和设计到前台页面使用的 ...

  3. 10、ASP.NET MVC入门到精通——Model(模型)和验证

    本系列目录:ASP.NET MVC4入门到精通系列目录汇总 模型就是处理业务,想要保存.创建.更新.删除的对象. 注解(通过特性实现) DisplayName Required StringLengt ...

  4. MyBatis从入门到精通(六):MyBatis动态Sql之if标签的用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用if标签生成动 ...

  5. CoreData 从入门到精通(二) 数据的增删改查

    在上篇博客中,讲了数据模型和 CoreData 栈的创建,那下一步就是对数据的操作了.和数据库一样,CoreData 里的操作也无非是增删改查.下面我们将逐步讲解在 CoreData 中进行增删改查的 ...

  6. Django模型修改及数据迁移

    Migrations Django中对Model进行修改是件麻烦的事情,syncdb命令仅仅创建数据库里还没有的表,它并不对已存在的数据表进行同步修改,也不处理数据模型的删除. 如果你新增或修改数据模 ...

  7. 创建app子应用,配置数据库,编写模型,进行数据迁移

    文章目录 web开发django模型 1.创建app子应用 2.配置子应用 3.使用 4.配置子应用管理自已的路由 django数据库开发思维与ORM 1.创建数据库 2.配置数据库 3.安装pymy ...

  8. CoreData 从入门到精通(三)关联表的创建

    上篇博客中讲了 CoreData 里增删改查的使用,学到这里已经可以应对简单的数据存储需求了.但是当数据模型复杂起来时,例如你的模型类中除了要存储 CoreData 里支持的数据类型外,还有一些自定义 ...

  9. CoreData 从入门到精通 (一) 数据模型 + CoreData 栈的创建

    CoreData 是 Cocoa 平台上用来管理模型层数据和数据持久化的一个框架,说简单点,就是一个数据库存储框架.CoreData 里相关的概念比较多,而且初始化也非常繁琐,所以对初学者的学习还是有 ...

随机推荐

  1. 2015.05.15,外语,学习笔记-《Word Power Made Easy》 01 “如何讨论人格特点”

    2015.03.17,外语,读书笔记-<Word Power Made Easy> 01 “如何讨论人格特点”学习笔记 SESSIONS 1 本来这些章节都是在一两年前学习的,现在趁给友人 ...

  2. oracle 11g rac 修改VIP、scan VIP、priv IP

    11GR2 RAC modify vip,public ip,private ip,scan vip实施步骤1 修改目的    根据业务的需求,需要由原来的临时IP改为生产ip,以下为调整前后对应的I ...

  3. xBIM 学习与应用系列目录

        xBIM 实战04 在WinForm窗体中实现IFC模型的加载与浏览   xBIM 实战03 使用WPF技术实现IFC模型的加载与浏览   xBIM 实战02 在浏览器中加载IFC模型文件并设 ...

  4. 动态连通性问题:union-find算法

    写在前面的话: 一枚自学Java和算法的工科妹子. 算法学习书目:算法(第四版) Robert Sedgewick 算法视频教程:Coursera  Algorithms Part1&2 本文 ...

  5. Rsync 服务器搭建

    Rsync简介 rsync 是一个 Unix 系统下的文件同步和传输工具. 它具备以下特性: 1. 能更新整个目录和树和文件系统 2. 有选择性的保持符号链链.硬链接.文件属于.权限.设备以及时间 等 ...

  6. HD-ACM算法专攻系列(22)——Max Sum

    问题描述: AC源码: 此题考察动态规划,解题思路:遍历(但有技巧),在于当前i各之和为负数时,直接选择以第i+1个为开头,在于当前i各之和为正数时,第i个可以不用作为开头(因为前i+1个之和一定大于 ...

  7. 洛谷P3357 最长k可重线段集问题(费用流)

    题目描述 给定平面 x-O-yx−O−y 上 nn 个开线段组成的集合 II ,和一个正整数 kk .试设计一个算法,从开线段集合 II 中选取出开线段集合 S\subseteq IS⊆I ,使得在  ...

  8. vue <router-view>没有渲染

    将routes中的components换成component

  9. poj 3160 Father Christmas flymouse【强连通 DAG spfa 】

    和上一道题一样,可以用DAG上的动态规划来做,也可以建立一个源点,用spfa来做 #include<cstdio> #include<cstring> #include< ...

  10. 七、利用frp 穿透到内网的http/https网站,实现对外开放

    有域名的话使用域名,没有域名的话使用IP注意80端口是否被已经安装使用的nginx占用,若被占用,可以换成其他端口,比如8080,,或者利用nginx的反向代理实现frp服务端与nginx共用80端口 ...