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

    然后我们再来看一下 新创建的 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 替换了。同时其他的数据也都没有丢失。

  • 相关阅读:
    day 66 crm(3) 自创组件stark界面展示数据
    day 65 crm(2) admin源码解析,以及简单的仿造admin组件
    用 Python+nginx+django 打造在线家庭影院
    django -admin 源码解析
    day 64 crm项目(1) admin组件的初识别以及应用
    云链接 接口不允许 情况 解决方法 mysql Host is not allowed to connect to this MySQL server解决方法
    day 56 linux的安装python3 ,虚拟环境,mysql ,redis
    day55 linux 基础以及系统优化
    Codeforces 989 P循环节01构造 ABCD连通块构造 思维对云遮月参考系坐标轴转换
    Codeforces 990 调和级数路灯贪心暴力 DFS生成树两子树差调水 GCD树连通块暴力
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/9841965.html
Copyright © 2020-2023  润新知