热门标签 | 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 替换了。同时其他的数据也都没有丢失。




推荐阅读
  • 本课程详细介绍了如何使用Python Flask框架从零开始构建鱼书应用,涵盖高级编程技巧和实战项目。通过视频教学,学员将学习到Flask的高效用法,包括数据库事务处理和书籍交易模型的实现。特别感谢AI资源网提供的课程下载支持。 ... [详细]
  • 成功实现Asp.Net MVC3网站与MongoDB数据库的高效集成
    我们成功地构建了一个基于Asp.NET MVC3框架的网站,并实现了与MongoDB数据库的高效集成。此次更新不仅完善了基本的创建和显示功能,还全面实现了数据的增删改查操作。在创建功能方面,我们修复了之前代码中的错误,确保每个属性都能正确生成。此外,我们还对数据模型进行了优化,以提高系统的性能和稳定性。 ... [详细]
  • 为了优化直播应用底部聊天框的弹出机制,确保在不同设备上的布局稳定性和兼容性,特别是在配备虚拟按键的设备上,我们对用户交互流程进行了调整。首次打开应用时,需先点击首个输入框以准确获取键盘高度,避免直接点击第二个输入框导致的整体布局挤压问题。此优化通过调整 `activity_main.xml` 布局文件实现,确保了更好的用户体验和界面适配。 ... [详细]
  • 深入解析:RKHunter与AIDE在入侵检测中的应用与优势
    本文深入探讨了RKHunter与AIDE在入侵检测领域的应用及其独特优势。通过对比分析,详细阐述了这两种工具在系统完整性验证、恶意软件检测及日志文件监控等方面的技术特点和实际效果,为安全管理人员提供了有效的防护策略建议。 ... [详细]
  • 本文将介绍一种扩展的ASP.NET MVC三层架构框架,并通过使用StructureMap实现依赖注入,以降低代码间的耦合度。该方法不仅能够提高代码的可维护性和可测试性,还能增强系统的灵活性和扩展性。通过具体实践案例,详细阐述了如何在实际开发中有效应用这一技术。 ... [详细]
  • 探讨 jBPM 数据库表结构设计的精要与实践
    探讨 jBPM 数据库表结构设计的精要与实践 ... [详细]
  • 在开发Xamarin.Forms应用程序时,遇到了使用Entity Framework Core 3.0访问SQLite数据库时 `Database.MigrateAsync` 方法调用的问题。本文详细探讨了该问题的根源,并提供了一种有效的解决方案,确保数据库迁移能够顺利执行。此外,还介绍了如何配置和优化EF Core以提高应用性能和稳定性。 ... [详细]
  • 本文详细探讨了代码中 `position` 属性的使用方法及其常见问题,并提出了多种有效的解决方案。通过实例分析,文章不仅解释了 `position` 属性的不同值(如 `static`、`relative`、`absolute` 和 `fixed`)在不同场景下的应用,还讨论了其对布局和定位的影响。此外,文章还提供了一些实用的调试技巧和最佳实践,帮助开发者更好地理解和应用这一重要 CSS 属性。 ... [详细]
  • MVVM架构~mvc,mvp,mvvm大话开篇
    返回目录百度百科的定义:MVP是从经典的模式MVC演变而来,它们的基本思想有相通的地方:ControllerPresenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模 ... [详细]
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • Android ListView 自定义 CheckBox 实现列表项多选功能详解
    本文详细介绍了在Android开发中如何在ListView的每一行添加CheckBox,以实现列表项的多选功能。用户不仅可以通过点击复选框来选择项目,还可以通过点击列表的任意一行来完成选中操作,提升了用户体验和操作便捷性。同时,文章还探讨了相关的事件处理机制和布局优化技巧,帮助开发者更好地实现这一功能。 ... [详细]
  • 本文深入探讨了NDK与JNI技术在实际项目中的应用及其学习路径。通过分析工程目录结构和关键代码示例,详细介绍了如何在Android开发中高效利用NDK和JNI,实现高性能计算和跨平台功能。同时,文章还提供了从基础概念到高级实践的系统学习指南,帮助开发者快速掌握这些关键技术。 ... [详细]
  • 利用ViewComponents在Asp.Net Core中构建高效分页组件
    通过运用 ViewComponents 技术,在 Asp.Net Core 中实现了高效的分页组件开发。本文详细介绍了如何通过创建 `PaginationViewComponent` 类并利用 `HelloWorld.DataContext` 上下文,实现对分页参数的定义与管理,从而提升 Web 应用程序的性能和用户体验。 ... [详细]
  • 如何在Android应用中设计和实现专业的启动欢迎界面(Splash Screen)
    在Android应用开发中,设计与实现一个专业的启动欢迎界面(Splash Screen)至关重要。尽管Android设计指南对使用Splash Screen的态度存在争议,但一个精心设计的启动界面不仅能提升用户体验,还能增强品牌识别度。本文将探讨如何在遵循最佳实践的同时,通过技术手段实现既美观又高效的启动欢迎界面,包括加载动画、过渡效果以及性能优化等方面。 ... [详细]
  • SSMS 启动故障:错误报告与解决求助 ... [详细]
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社区 版权所有