CoreData 与 CloudKit:模型迁移后,所有内容都会重复

问题描述 投票:0回答:1

我尝试迁移我的 CoreData-Model(使用 CloudKit),它复制了我存储的所有对象。在 CloudKit 中使用 CoreData 时如何正确迁移?

总结

我正在将 CoreData 与 CloudKit 结合使用。几天前,我对模型做了一些更改,因此需要迁移。事情就是这样(详情见下文):

  1. 我刚刚在我的模型中进行了更改 (

    Model.xcdatamodel
    ),没有更改模型的版本,并将其安装在我的 iPhone 上进行测试 -> 崩溃并显示消息“无法就地迁移存储:尝试迁移期间违反约束” .

  2. 我创建了模型的新版本(

    Model 2.xcdatamodel
    )并在那里进行了更改。然后我创建了一个
    .xcmappingmodel
    来管理迁移。没有崩溃并且它有效,但是......

  3. 我的应用程序中的所有条目现在都是重复的,这当然不是预期的。

我在模型中更改了什么:

我的原始(源)模型有两个实体 A 和 B。A 和 B 之间存在多对多映射。我做了以下更改。

  • 添加两个新实体 C 和 D,带有一个数据字段(“名称”)
  • 在两个新实体 C、D 和我现有的实体之一 (A) 之间创建一对多映射

我只是创建了

.xcmappingmodel
文件,没有更改其中的任何内容。对于现有实体 A 和 B,它具有接管先前数据的条目,如下所示:

destination attribute: name
value expression: $source.name

对于现有的映射A-B(实体B称为“标签”),它有:

FUNCTION($manager, "destinationInstancesForEntityMappingNamed:sourceInstances:" , "TagToTag", $source.tags)
反比关系也类似。

我如何构建我的 CoreData 堆栈

我遵循了 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
}()
[...]

我真的不知道我做错了什么或如何解决这个问题。如果有人能指出我正确的方向,我将不胜感激。

ios swift core-data cloudkit core-data-migration
1个回答
0
投票

我们有同样的问题,似乎每次我们使用映射模型进行重量级模型迁移时都会发生。经过大量研究,看来您/我们没有做错了什么。

Apple CloudKit 文档似乎表明数据重复是有意设计的,以便访问核心数据的旧版本应用程序将能够使用旧数据。这可能就是CloudKit中不允许唯一约束的原因(我们不确定)。请参阅此Apple CloudKit 文档。请注意标题为:更新生产架构的部分以及向核心数据实体添加版本号的建议。

  • 向现有记录类型增量添加新字段。如果您采用这种方法,旧版本的应用程序可以访问用户创建的每条记录,但不能访问每个字段。
  • 通过从一开始就包含版本属性来对实体进行版本控制,并使用获取请求仅选择与应用程序当前版本兼容的记录。如果您采用这种方法,旧版本的应用程序将不会获取用户使用较新版本创建的记录,从而有效地将它们隐藏在该设备上。

因此我们得出的结论是,唯一的解决方案是执行以下一项或两项操作:

  1. 对我们的核心数据实体进行版本控制,并让较新版本的软件在获取请求期间过滤掉旧模型实体。
  2. 编写代码来手动删除旧记录,但这可能会给在因操作系统不兼容而无法更新的设备上运行旧版本软件的用户带来问题。

有一些使用

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")
}
© www.soinside.com 2019 - 2024. All rights reserved.