热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

coredata数据库升级

在真实开发中,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模的重构;

在真实开发中,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模的重构;所以数据的迁移就显得尤为重要了。
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 才会自动推断。

注意 :这里的renamingid何时使用呢?就是如果说原来的字段比如叫做A, 新的数据库想把名字改为B,但是值还是之前的,那么就需要在新的数据库中设置这个renamingid的值,如果原来的对应的字段没有设置renamingid,那么默认就需要在新的数据库字段的renamingid一栏写成原来数据库对应的字段的名字。如果原来的字段也设置了renamingid,那么就需要在新的里面也要写上这个renamingid,即新的数据库和旧的数据库同一字段的renamingid也一致,才能达到只改字段名字的效果。

下面我们来看一下,怎么使用自动推断。这是初始版本的 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

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

  1. - (void)insertManyStudents {
  2. NSSet *science = [self scienceCourses];
  3. NSSet *art = [self artCourses];
  4. Clazz *clazz1 = [[Clazz alloc] initWithContext:self.persistentContainer.viewContext];
  5. clazz1.clazzName = @"文科一班";
  6. clazz1.classId = 1;
  7. Clazz *clazz2 = [[Clazz alloc] initWithContext:self.persistentContainer.viewContext];
  8. clazz2.clazzName = @"理科一班";
  9. clazz2.classId = 2;
  10. for (NSUInteger i &#61; 0; i <300; i&#43;&#43;) {
  11. NSString *name &#61; [NSString stringWithFormat:&#64;"student-%u", arc4random_uniform(100000)];
  12. int16_t age &#61; (int16_t)arc4random_uniform(10) &#43; 10;
  13. int16_t stuId &#61; (int16_t)arc4random_uniform(INT16_MAX);
  14. Student *student &#61; [NSEntityDescription insertNewObjectForEntityForName:&#64;"Student" inManagedObjectContext:self.persistentContainer.viewContext];
  15. student.name &#61; name;
  16. student.age &#61; age;
  17. student.id &#61; stuId;
  18. if (i % 2 &#61;&#61; 0) {
  19. student.clazz &#61; clazz1;
  20. student.courses &#61; art;
  21. } else {
  22. student.clazz &#61; clazz2;
  23. student.courses &#61; science;
  24. }
  25. }
  26. NSError *error;
  27. [self.persistentContainer.viewContext save:&error];
  28. }
  29. - (NSSet *)scienceCourses {
  30. Course *physics &#61; [[Course alloc] initWithContext:self.persistentContainer.viewContext];
  31. physics.courseName &#61; &#64;"物理";
  32. physics.courseId &#61; 1;
  33. physics.courseChapterCount &#61; 5;
  34. Course *chemistry &#61; [[Course alloc] initWithContext:self.persistentContainer.viewContext];
  35. chemistry.courseName &#61; &#64;"化学";
  36. chemistry.courseId &#61; 2;
  37. chemistry.courseChapterCount &#61; 9;
  38. Course *biology &#61; [[Course alloc] initWithContext:self.persistentContainer.viewContext];
  39. biology.courseName &#61; &#64;"生物";
  40. biology.courseId &#61; 3;
  41. biology.courseChapterCount &#61; 10;
  42. NSSet *courses &#61; [NSSet setWithObjects:physics, chemistry, biology, nil];
  43. return courses;
  44. }
  45. - (NSSet *)artCourses {
  46. Course *chinese &#61; [[Course alloc] initWithContext:self.persistentContainer.viewContext];
  47. chinese.courseName &#61; &#64;"语文";
  48. chinese.courseId &#61; 4;
  49. chinese.courseChapterCount &#61; 12;
  50. Course *history &#61; [[Course alloc] initWithContext:self.persistentContainer.viewContext];
  51. history.courseName &#61; &#64;"历史";
  52. history.courseId &#61; 5;
  53. history.courseChapterCount &#61; 19;
  54. Course *geography &#61; [[Course alloc] initWithContext:self.persistentContainer.viewContext];
  55. geography.courseName &#61; &#64;"地理";
  56. geography.courseId &#61; 6;
  57. geography.courseChapterCount &#61; 21;
  58. return [NSSet setWithObjects:chinese, geography, history, nil];
  59. }

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


Student-table-w600


Course 表


Course-table-w600

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

接下来创建 Mapping Model 文件


Mapping-Model-w600

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


Source-data-model-w600

Target-data-model-w600

最后保存的文件名建议按一定的规则来命名&#xff0c;后期也方便查找&#xff1a;


Save-mapping-model-w600

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


StudentToStudent-mapping-w600


当然&#xff0c;如果字段名修改过的话&#xff0c;同样是不能推断出来的&#xff0c;如 Course 表的字段&#xff1a;


CourseToCourse-mapping-w600


另外每个 Entity Mapping 的名字的命名规则是以 SourceEntityNameToDestinationEntityName 来命名的&#xff0c;这个可以在右侧的面板中修改&#xff1a;


Entity-Mapping-name-w600

下面来介绍 mapping model 中会用到的几个对象&#xff1a;

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

除了这两个&#xff0c;还有几个不常用的&#xff1a;

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

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


-w600

-w600

然后再来看 Relationship Mapping 映射&#xff1a;


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

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

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


-w600

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


Student-table-v3-w600

Course 表&#xff1a;


Course-table-v3-w600

自动生成的 Course 和 Student 之间的关联表&#xff1a;


Students-table-v3-w600

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




推荐阅读
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • MongoDB集群配置:副本集与分片详解
    本文详细介绍了如何在MongoDB中配置副本集(Replica Sets)和分片(Sharding),并提供了具体的步骤和命令,帮助读者理解并实现高可用性和水平扩展的MongoDB集群。 ... [详细]
  • 本文详细解析了Python中的os和sys模块,介绍了它们的功能、常用方法及其在实际编程中的应用。 ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
author-avatar
mmmmmmmmmm0000
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有