我尝试迁移我的 CoreData-Model(使用 CloudKit),它复制了我存储的所有对象。在 CloudKit 中使用 CoreData 时如何正确迁移?
我正在将 CoreData 与 CloudKit 结合使用。几天前,我对模型做了一些更改,因此需要迁移。事情就是这样(详情见下文):
我刚刚在我的模型中进行了更改 (
Model.xcdatamodel
),没有更改模型的版本,并将其安装在我的 iPhone 上进行测试 -> 崩溃并显示消息“无法就地迁移存储:尝试迁移期间违反约束” .
我创建了模型的新版本(
Model 2.xcdatamodel
)并在那里进行了更改。然后我创建了一个 .xcmappingmodel
来管理迁移。没有崩溃并且它有效,但是......
我的应用程序中的所有条目现在都是重复的,这当然不是预期的。
我的原始(源)模型有两个实体 A 和 B。A 和 B 之间存在多对多映射。我做了以下更改。
我只是创建了
.xcmappingmodel
文件,没有更改其中的任何内容。对于现有实体 A 和 B,它具有接管先前数据的条目,如下所示:
destination attribute: name
value expression: $source.name
对于现有的映射A-B(实体B称为“标签”),它有:
FUNCTION($manager, "destinationInstancesForEntityMappingNamed:sourceInstances:" , "TagToTag", $source.tags)
反比关系也类似。
我遵循了 Apple 的文档。我的代码看起来像这样(我做了一个
CoreDataManager
类):
[...]
lazy var persistentContainer: NSPersistentContainer = {
let container: NSPersistentContainer
container = NSPersistentCloudKitContainer(name: containerName)
let storeDescription = container.persistentStoreDescriptions.first
storeDescription?.type = NSSQLiteStoreType
container.loadPersistentStores { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error when loading CoreData persistent stores: \(error), \(error.userInfo)")
}
}
return container
}()
lazy var mainContext: NSManagedObjectContext = {
let context = self.persistentContainer.viewContext
context.automaticallyMergesChangesFromParent = true
return context
}()
[...]
我真的不知道我做错了什么或如何解决这个问题。如果有人能指出我正确的方向,我将不胜感激。
我们有同样的问题,似乎每次我们使用映射模型进行重量级模型迁移时都会发生。经过大量研究,看来您/我们没有做错了什么。
Apple CloudKit 文档似乎表明数据重复是有意设计的,以便访问核心数据的旧版本应用程序将能够使用旧数据。这可能就是CloudKit中不允许唯一约束的原因(我们不确定)。请参阅此Apple CloudKit 文档。请注意标题为:更新生产架构的部分以及向核心数据实体添加版本号的建议。
- 向现有记录类型增量添加新字段。如果您采用这种方法,旧版本的应用程序可以访问用户创建的每条记录,但不能访问每个字段。
- 通过从一开始就包含版本属性来对实体进行版本控制,并使用获取请求仅选择与应用程序当前版本兼容的记录。如果您采用这种方法,旧版本的应用程序将不会获取用户使用较新版本创建的记录,从而有效地将它们隐藏在该设备上。
因此我们得出的结论是,唯一的解决方案是执行以下一项或两项操作:
有一些使用
NSPersistentHistoryTrackingKey
的解决方案,但这些解决方案看起来相当复杂,我们不清楚额外的复杂性与简单地使用我们幸运地拥有的 UUID 编写一些简单的重复数据删除代码相比有什么优势。我们的模型实体。
每次应用程序启动并初始化核心数据时都可以简单地调用该代码。我们每次都会这样做,因为我们的软件在 MacOS 和 iOS 设备上运行,并在所有版本之间共享数据,并且无法知道其他设备何时会升级到较新的数据模型并复制数据。去重的代码很简单:
func deDuplicateAfterMigrationFrom14to15()
{
print("**** CoreDataUtil DeDupe UUIDs without new attribute or too many with new")
let moc = pc.viewContext
let chartsFetch = NSFetchRequest<NSFetchRequestResult>(entityName:"Charts") // Fetch all charts
do {
let fetchedCharts = try moc.fetch(chartsFetch) as! [Chart]
for chart in fetchedCharts
{
// Find and Remove duplicate UUID
chartsFetch.predicate = NSPredicate(format:"uuid == %@", chart.uuid)
do {
let fetchedChartsWithUUID = try moc.fetch(chartsFetch) as! [Chart]
if(fetchedChartsWithUUID.count > 1) {
for(index, chartWithUUID) in fetchedChartsWithUUID.enumerated() {
// Find old Entity without new attribute
let nameFirstChar = chartWithUUID.nameFirstChar ?? ""
if(nameFirstChar.isEmpty) {
print("***** DeDupe OLD Chart UUID-> " + chartWithUUID.uuid + " NAME[\(index)]-> " + chartWithUUID.name)
self.pc.viewContext.delete(chartWithUUID)
}
// Find extra copies of updated Entity created by other devices.
else if(!nameFirstChar.isEmpty && index > 1) {
print("***** DeDupe NEW Extra Chart UUID-> " + chartWithUUID.uuid + " NAME[\(index)]-> " + chartWithUUID.name)
self.pc.viewContext.delete(chartWithUUID)
}
}
try moc.save()
}
}
catch {
print("****** De-Dupe UUID Failed for UUID?: \(error)")
}
}
try moc.save()
}
catch {
print("****** De-Dupe initial Fetch failed: \(error)")
}
print("**** CoreDataUtil De-Dupe DONE")
}