coredata 数据库升级
在真实开发中,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模的重构;所以数据的迁移就显得尤为重要了。
CoreData 中,数据迁移本质就是把旧的 SQLite 数据库里的内容,复制到新的 SQLite
数据库里去,让新的数据库作为默认的数据存储。伴随着模型版本的变化,新旧两个数据库的实体结构当然也是不同的。这就是说在迁移过程中必须知道新旧两个数据库的模型对应关系,旧数据库里的数据该怎么复制到新的数据库中。这在
CoreData 中是由 MappingModel 映射模型来决定的。我们所需要做的就是创建 MappingModel
文件,指定好实体不同版本间的映射,CoreData 就会自动帮我们完成数据迁移。当然如果模型版本的变化比较小,CoreData
是可以自动推断出映射模型的。下面就来详细的介绍一下 CoreData 里常用的几种迁移。
创建模型版本
在介绍数据迁移之前,先来看如何创建新的模型版本,在 Xcode 里模型是通过 .xcdatamodeld
文件来创建的,实际上这个文件就是一个包,里面可以包含不同的模型版本。选中这个文件,然后点击 Editor->Add Model
Version... 就可以添加一个新的模型版本。
然后会弹出下面这个对话框,默认的新的模型会在原来的基础上增加一个数字,来标识不同的模型版本。这个数字也是可以更改的,你可以按照自己的喜好更改成 v2 或者其他的。
点击 finish 后就会看到现在的
LearnCoreData.xcdatamodeld文件可以展开了,里面包含了所有的模型版本文件,它们是 xcdatamodel
格式的。在右侧的 File Inspector 面板中可以指定当前的模型版本,然后程序打包后就会把选中的模型版本作为当前的默认版本。
自动推断映射模型
上面说到对于一些较小的变化,CoreData 是可以自动推断映射模型的,从而帮助我们自动地完成数据迁移。针对下面这些改动,CoreData 都可以自动的进行推断:
- 添加一个属性
- 移除一个属性
- 非空的属性变成可以为空的
- 可以为空的属性变成非空属性并设置一个默认值
- 重命名实体或者属性(需要设置 renaming identifier)
- 添加/删除 RelationShip
- 重命名 RelationShip(需要设置 renaming identifier)
- 把一个 RelationShip 从 对一改成对多,或者把非排序的改成排序的。(反过来也是可以的)
上面说到的 renaming identifier 可以在 Model Inspector 进行设置,对不同版本的对应实体/属性设置相同的 Renaming ID,CoreData 就可以自动推断出对应的映射模型。
除此之外,在向 persistentStoreCoordinator 调用 addPersistentStoreWithType:configuration:URL:options:error: 添加
persistentStore时,需要将 options 的 NSMigratePersistentStoresAutomaticallyOption 和
NSInferMappingModelAutomaticallyOption 两个 key 设置为 YES,CoreData 才会自动推断。
注意
:这里的renamingid何时使用呢?就是如果说原来的字段比如叫做A,
新的数据库想把名字改为B,但是值还是之前的,那么就需要在新的数据库中设置这个renamingid的值,如果原来的对应的字段没有设置renamingid,那么默认就需要在新的数据库字段的renamingid一栏写成原来数据库对应的字段的名字。如果原来的字段也设置了renamingid,那么就需要在新的里面也要写上这个renamingid,即新的数据库和旧的数据库同一字段的renamingid也一致,才能达到只改字段名字的效果。
下面我们来看一下,怎么使用自动推断。这是初始版本的 StudentEntity 实体的结构:
下面我们再创建一个 Model Version,把原来的 StudentEntity、ClassEntity、CourseEntity 的
EntityName 分别修改成 Student、Clazz、Course;Student 里面的字段修改成 name、id 和
age,另外再添加一个 BOOL 字段 sex,表示性别,默认值设置为 YES。
然后为两个版本中修改过的实体名字和属性字段名字设置相同的 renaming identifier。以 Student 的 name 字段为例,旧版的模型中:
然后新版本的模型中:
修改好后,暂时我们先不切换到新版本的模型中,先用旧的数据库生成一些测试数据,然后在沙盒的 Library/Application
Support/ 目录里复制出里边的三个文件,然后用 SQLite 工具打开 .sqlite 的数据库文件查看数据库的的结构,和刚存进去的内容。
这是打开后的 StudentEntity 表,里面随机插入了 300 条数据,注意到现在由我们创建的几个字段分别是 ZSTUDENTID、ZSTUDENTCLASS、ZSTUDENTNAME。
现在我们把数据库切换到新版中,然后再运行一次程序,重新打开新生成的数据库文件,就会看到新版的数据库的结构:
现在 StudentEntity 已经变成了 Student,每个字段也都变成了新的字段名,而且里面也多了我们添加的 sex 字段。这就说明 CoreData 的自动推断成功了。
自定义映射模型
大多数情况下自动推断就能帮我们完成数据的迁移,但当数据的变化更复杂时,例如如果我们把 Student
里的一个字段提取出来放到一个新的字段中去。就得靠我们手动创建 mapping model 了。例如我们现在想把上面 Clazz 表删除,原来的
Student 中的 clazz 字段用 clazzName 字段来代替。那么这种情况下就需要手动来创建 mapping model 了。
在这之前我们先用旧版的数据模型插入一些示例的数据,这是插入的 Student 数据:
Clazz 数据:
Course 数据:
因为 Course 和 Student 是多对多的关系,所以还会有一张关联表:
这是插入示例数据的代码:
- - (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 表
Course 表
这一次我们不再创建 Clazz 表了,因为它要被 Student 表里的 clazzName 字段代替。
接下来创建 Mapping Model 文件
创建过程中需要选择 Source data model 和 Destination data model,也就是迁移的旧版和新版数据模型版本,分别选择 v2 和 v3 版本:
最后保存的文件名建议按一定的规则来命名,后期也方便查找:
然后我们来认识一下 mapping model 的用法,创建好后,mapping model 还是会自动推断出大多数的字段映射,例如 Student 表中除新添加的 clazzName 字段外,其他的都可以正确的推断出来;
当然,如果字段名修改过的话,同样是不能推断出来的,如 Course 表的字段:
另外每个 Entity Mapping 的名字的命名规则是以 SourceEntityNameToDestinationEntityName 来命名的,这个可以在右侧的面板中修改:
下面来介绍 mapping model 中会用到的几个对象:
- $source - 对应着 NSMigrationSourceObjectKey,可以理解为 Source Model 的一个实体对象
- $manager - 对应着 NSMigrationManagerKey,它代表的是 NSMigrationManager 对象,正是这个对象在迁移过程中发挥着作用,它管理着源对象和目标对象之间的关联
除了这两个,还有几个不常用的:
- $destination -- NSMigrationDestinationObjectKey
- $entityMapping -- NSMigrationEntityMappingKey
- $propertyMapping -- NSMigrationPropertyMappingKey
- $entityPolicy -- NSMigrationEntityPolicyKey
在 mapping model 中可以通过 \$ 加对应的名字,直接访问这几个对象。例如上面图中 \$source.name 就代表源对象的 name 属性。同样的我们就可以把其他未推断出来的填上:
然后再来看 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 关系:
所有字段都配置完后,就可以把 模型版本切换都 v3 然后运行程序。程序在运行时发现当前的 v3 版本数据模型和本地存储的 v2
数据库版本不一致,就会自动从 bundle 里寻找对应 v2 到 v3 的 Mapping Model,依据自定义的 Mapping
Model,数据就会自动迁移完成。
下面来看一下迁移完成的 v3 版本数据库。
Student 表:
Course 表:
自动生成的 Course 和 Student 之间的关联表:
可以看到 Student 表中的 clazz 字段已经被 clazzName 替换了。同时其他的数据也都没有丢失。
coredata 数据库升级的更多相关文章
- CoreData数据库升级
如果IOS App 使用到CoreData,并且在上一个版本上有数据库更新(新增表.字段等操作),那在覆盖安装程序时就要进行CoreData数据库的迁移,具体操作如下: 1.选中你的mydata.xc ...
- iOS coredata 数据库升级 时报Can't find model for source store
在coredata 数据库结构被更改后,没根据要求立即建立新version,而是在原version上进行了小修改,之后才想起来建立新版本.并通过以下代码合并数据库, NSError *error = ...
- CoreData 数据库
封装CoreManager类 @implementation CoreDataManager { //上下文 NSManagedObjectContext *_ctx; } //单例 +(instan ...
- CoreData(数据库升级 )版本迁移-iOS App升级安装
版权声明:本文为博主原创文章,未经博主允许不得转载. 如果IOS App 使用到CoreData,并且在上一个版本上有数据库更新(新增表.字段等操作),那在覆盖安装程序时就要进行CoreData数据库 ...
- Oracle数据库升级(10.2.0.4->11.2.0.4)
环境: RHEL5.4 + Oracle 10.2.0.4 目的: 在本机将数据库升级到11.2.0.4 之前总结的Oracle数据库异机升级:http://www.cnblogs.com/jyzha ...
- 生产环境中,数据库升级维护的最佳解决方案flyway
官网:https://flywaydb.org/ 转载:http://casheen.iteye.com/blog/1749916 1. 引言 想到要管理数据库的版本,是在实际产品中遇到问题后想到的 ...
- iOS - CoreData 数据库存储
1.CoreData 数据库 CoreData 是 iOS SDK 里的一个很强大的框架,允许程序员以面向对象的方式储存和管理数据.使用 CoreData 框架,程序员可以很轻松有效地通过面向对象的接 ...
- CoreData数据库
一 CoreData 了解 1 CoreData 数据持久化框架是 Cocoa API 的一部分,首先在iOSS5 版本的系统中出现: 它允许按照 实体-属性-值 模式组织数据: ...
- CoreData数据库迁移的操作
CoreData数据库迁移操作步骤,操作是基于Xcode7. 1.添加新的数据库.选中当前数据库版本:Editor->Add Model Verson,创建一个新的数据库版本. 2.Comman ...
随机推荐
- golang 开发过程中的坑
1. chan数据读取写入 正常情况下chan读取写入都没有问题,但是如果chan关闭之后会出现问题 所以读取chan数据的时候需要增加chan是否关闭的判断 c := make(chan ) v, ...
- 《挑战程序设计竞赛》2.3 动态规划-基础 POJ3176 2229 2385 3616 3280
POJ3176 Cow Bowling 题意 输入一个n层的三角形,第i层有i个数,求从第1层到第n层的所有路线中,权值之和最大的路线. 规定:第i层的某个数只能连线走到第i+1层中与它位置相邻的两个 ...
- Exchange Version and UpdateRollups
Exchange Server 2010 Product name Build number Date KB Microsoft Exchange Server 2010 RTM 14.0.639.2 ...
- hadoop笔记 基础 归档
核心:分布式存储和分布式计算 闲话: 底层基于socket通信 NIO——java异步io,不阻塞,不等待 bt——p2p软件(点对点传输,每个人既是上传者又是下载者.但是会占用大量网络带宽,所以很多 ...
- pycharm中选择python interpreter
pycharm中选择python interpreter pycharm中有两处地方需要选择python解释器: 一处是调试配置(edit configurations)处,这里选择python解释器 ...
- 商业模式画布模板——From 《商业模式新生代》
看过<商业模式新生代>这本书,确实受益匪浅.书籍本身编写的形式很新颖,以此为模板可以启发自己对于商业模式的思考和定义,五星推荐!!! 下面是用PPT重新绘制的商业模式画布以及说明,希望对大 ...
- 购物单问题—WPS使用excel
**** 180.90 88折 **** 10.25 65折 **** 56.14 9折 **** 104.65 ...
- 单文件快速体验使用react输出hello_world
看了下react官方的hello world教程, 感觉对新手很不友好.codepen虽然好用, 但是封装太多东西, 看起来 太抽象. 还是喜欢像学习jQuery那样, 直接在单文件中引入必要的js文 ...
- Django之查询总结
models.Book.objects.filter(**kwargs): querySet [obj1,obj2]models.Book.objects.filter(**kwargs).value ...
- MyIBatis使用
1.如果根据一些Id进行删除,那么会用到In的用法如: <delete id="DeleteByIds" parameterClass="UserInfo" ...