核心数据迁移和单元测试
核心数据迁移
核心数据迁移有两种类型: 轻量级迁移和重量级迁移 。
核心数据可以通过对数据库架构的一些简单更改来执行自动数据迁移。 为了执行自动迁移,核心数据必须能够推断旧模式与新模式之间的映射。 核心数据参考支持以下修改:
- 添加或删除属性
- 非可选属性变为可选,反之亦然
- 重命名实体,属性或关系
有关受支持的轻量级迁移的更多信息,请参见《 核心数据模型版本控制和数据迁移编程指南》 。
苹果推荐
但是,随着项目的增长,架构可能变得过于复杂,以至于核心数据无法执行自动迁移。 在这种情况下,您必须编写自己的迁移。 通过创建核心 数据映射模型并实现NSEntityMigrationPolicy
。 基本上,迁移策略将使我们能够访问两个版本的托管对象上下文 ,从而使我们能够对实体及其关系进行任何自定义。 这是重量级迁移 ,它更复杂,但功能也更强大。
在本教程中,我将向您展示编写重量级迁移的所有必要步骤,以及有关对其执行单元测试所需的所有知识。
工作流程如下:
首先,我们需要生成数据的旧版本。 然后,我们对该数据执行数据库迁移,并获得该数据的新版本。 最后,我们将新版本的数据与旧版本的数据进行比较,看看是否有任何意外的数据。
准备中
在此示例中,我们创建模式的两个版本。 第一个版本仅包含一个Person对象,并且有一个属性类型指示此人是学生还是老师。 稍后,我们决定将Person表拆分为两个表: Student表和Teacher表。 这是轻量级核心数据迁移无法处理的事情。
- 版本1
- 版本2
为了处理这种迁移,我们创建了一个映射模型 。
Xcode已经生成了一些映射策略。 我们将丢弃它们并创建我们自己的。 我将其称为PersonToTeacherStudent并将自定义策略设置为PersonToTeacherStudentPolicy 。 重要的是要知道我们必须从NSEntityMigrationPolicy
创建一个对应的类PersonToTeacherStudentPolicy子类。
这是处理拆分的策略类:
导入CoreData
类PersonToTeacherStudentPolicy:NSEntityMigrationPolicy {
覆盖func createDestinationInstancesForSourceInstance(sInstance:NSManagedObject,
实体映射:NSEntityMapping,
经理:NSMigrationManager)抛出
{
如果sInstance.entity.name ==“人”
{
让firstname = sInstance.primitiveValueForKey(“ firstname”)为! 串
让lastname = sInstance.primitiveValueForKey(“ lastname”)为! 串
让type = sInstance.primitiveValueForKey(“ type”)为! 整数
如果类型== 0 {
让person2 = NSEntityDescription.insertNewObjectForEntityForName(“ Teacher”,
inManagedObjectContext:manager.destinationContext)
person2.setValue(firstname,forKey:“名字”)
person2.setValue(lastname,forKey:“ lastname”)
}其他{
让person2 = NSEntityDescription.insertNewObjectForEntityForName(“ Student”,
inManagedObjectContext:manager.destinationContext)
person2.setValue(firstname,forKey:“名字”)
person2.setValue(lastname,forKey:“ lastname”)
}
}
}
}
生成测试数据
关键步骤是使旧模式NSManagedObjectModel
生成旧模式的测试数据。 我们将从获取旧版本的模型架构开始。 然后我们可以基于此生成一些数据。 重要的是要知道这些数据也可以从SQL数据文件中加载。 但是,此方法存在问题,因为单元测试将依赖于某些预设状态。 在直接在单元测试代码中生成数据的同时,我们能够准确地看到已创建的内容,并且以后也更容易调试。 在下面的示例中,我们创建两个Person
,一个类型等于0的Teacher
和一个类型等于1的Student
。
//
//生成测试数据
//
让oldModelUrl = NSBundle.mainBundle()。URLForResource(“ CoreDataExample.momd / CoreDataExample”,
withExtension:“妈妈”)!
让oldManagedObjectModel = NSManagedObjectModel.init(contentsOfURL:oldModelUrl)
XCTAssertNotNil(oldManagedObjectModel)
让coordinator = NSPersistentStoreCoordinator.init(managedObjectModel:oldManagedObjectModel!)
让url = self.applicationDocumentsDirectory.URLByAppendingPathComponent(“ SingleViewCoreData.sqlite”)
尝试! coordinator.addPersistentStoreWithType(NSSQLiteStoreType,配置:无,URL:网址,选项:无)
让managedObjectContext = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator =协调器
让person = NSEntityDescription.insertNewObjectForEntityForName(“ Person”,
inManagedObjectContext:managedObjectContext)
person.setValue(“ John”,forKey:“名字”)
person.setValue(“ Smith”,forKey:“姓氏”)
person.setValue(0,forKey:“ type”)
让person2 = NSEntityDescription.insertNewObjectForEntityForName(“ Person”,
inManagedObjectContext:managedObjectContext)
person2.setValue(“ Lily”,forKey:“名字”)
person2.setValue(“ Brown”,forKey:“ lastname”)
person2.setValue(1,forKey:“ type”)
尝试! managedObjectContext.save()
执行迁移
在获得这些数据之后,我们对其进行迁移,并获得了较新版本的数据。 这是非常简单的。
//
// 移民
//
让newModelUrl = NSBundle.mainBundle()。URLForResource(“ CoreDataExample.momd / CoreDataExample 1.1”,
withExtension:“妈妈”)!
让newManagedObjectModel = NSManagedObjectModel.init(contentsOfURL:newModelUrl)
XCTAssertNotNil(newManagedObjectModel)
让mappingModel = NSMappingModel.init(fromBundles:nil,forSourceModel:oldManagedObjectModel,
destinationModel:newManagedObjectModel)
XCTAssertNotNil(mappingModel)
让migrationManager = NSMigrationManager.init(sourceModel:oldManagedObjectModel !,
destinationModel:newManagedObjectModel!)
让newUrl = self.applicationDocumentsDirectory.URLByAppendingPathComponent(“ SingleViewCoreData1.1.sqlite”)
尝试! migrationManager.migrateStoreFromURL(URL,
类型:NSSQLiteStoreType,
选项:无,
withMappingModel:mappingModel,
toDestinationURL:newUrl,
destinationType:NSSQLiteStoreType,
destinationOptions:无)
验证数据
为了简单firstname
,我们没有验证firstname
和lastname
。 但是以下内容应使您对其工作原理有所了解。 迁移完成后,应该有1位老师和1位学生。 由于它们都是从人员表派生的,因此我们仍然应该有2个人。 这就是以下代码正在验证的内容:
//
//验证
//
让newOoordinator = NSPersistentStoreCoordinator.init(managedObjectModel:newManagedObjectModel!)
尝试! newOoordinator.addPersistentStoreWithType(NSSQLiteStoreType,
配置:无,URL:newUrl,选项:无)
让newManagedObjectContext = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)
newManagedObjectContext.persistentStoreCoordinator = newOoordinator
让studentRequest = NSFetchRequest.init(entityName:“学生”)
XCTAssertEqual(尝试!newManagedObjectContext.executeFetchRequest(studentRequest).count,1)
让personRequest = NSFetchRequest.init(entityName:“ Person”)
XCTAssertEqual(尝试!newManagedObjectContext.executeFetchRequest(personRequest).count,2)
让TeacherRequest = NSFetchRequest.init(entityName:“ Teacher”)
XCTAssertEqual(尝试!newManagedObjectContext.executeFetchRequest(teacherRequest).count,1)
摘要
没有单元测试,迁移的开发过程如下所示:
- 运行旧版本的应用并生成一些数据
- 更新数据库架构
- 重新启动应用程序并迁移数据
- 验证迁移结果
我们需要不断地往返 步骤1到步骤3。例如,我们可以从迁移一张表开始。 稍后,我们还需要对其他表进行更改。 而且我们的迁移代码可能有效也可能无效,在这种情况下,我们还需要多次检查。
使用单元测试的好处是,如果在添加新代码时破坏了以前的任何逻辑,我们将通过回归测试立即注意到它。 而且,在开发过程中,我们不需要经常签出该应用程序的先前版本并手动生成测试数据,这可节省大量时间。