Realm Swift Error: Embedded objects cannot be created directly

Realm Swift Error: Embedded objects cannot be created directly

我正在尝试将 属性 类型 List<String> 的对象迁移为类型 List<ChildObject>,其中 ChildObject 是自定义 EmbeddedObject

例子

我的意思是:

import RealmSwift

final class ParentObject: Object {
    // Previously, this property was of type `List<String>`.
    @Persisted public var children: List<ChildObject>
}

final class ChildObject: EmbeddedObject {
    @Persisted var name = ""
}

我正在使用此代码执行迁移,这会产生错误:

Embedded objects cannot be created directly

let configuration = Realm.Configuration(schemaVersion: 1) { migration, oldSchemaVersion in
    if oldSchemaVersion < 1 {
        migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
            let childrenStrings = oldObject!["children"] as! List<DynamicObject>
            let childrenObjects = newObject!["children"] as! List<MigrationObject>

            // I'm trying to retain the previous values for `children` (of type `String`) 
            // where each value is used as the `name` property of a new `ChildObject`.
            for string in childrenStrings {
                childrenObjects.append(
                    // This line produces the error :(
                    migration.create(ChildObject.className(), value: [string])
                )
            }
        }
    }
}

let realm = try! Realm(configuration: configuration)

问题

如何在保留以前的值的同时执行迁移?

最简单的方法是用每个子对象的所有 属性 name/value 对创建一个 Dictionary,然后完整地创建新的 List用这些对组成的数组。

但首先您需要从 children 的旧 List 中提取 String 值。这是必要的原因是因为 Realm 不将 List<String> 的元素表示为实际的 String(这是一个结构)。我们可以从 description 字段中提取实际值:

childrenStrings
    .map { String(describing: [=10=]) }

一旦你有了名字,你就可以用 Dictionary 代表新的 ChildObject。请注意,您必须在 Dictionary 中包含所有 属性 名称和值。由于我们只有一个,称为“名称”,因此我们包括:

childrenStrings
    .map { String(describing: [=11=]) }
    .map { ["name": [=11=]] }

您的错误消息说:

Embedded objects cannot be created directly

但是,您可以使用 DictionaryArray 创建新对象,其中 Array 对应于 List,每个 Dictionary 对应于一个 ChildObject 对象。把它们放在一起我们有:

migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
    let childrenStrings = oldObject!["children"] as! List<DynamicObject>

    newObject?.setValue(
        Array(
            childrenStrings
                .map { String(describing: [=12=]) }
                .map { ["name": [=12=]] }
        ),
        forKey: "children"
    )
}

总结一下 Rob 和 Jay 的解决方案:

选项 1

这是 Rob 修改后的解决方案。

migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
    let childrenStrings = oldObject!["children"] as! List<DynamicObject>

    newObject!.setValue(
        Array(childrenStrings.map { [[=10=]] }), 
        forKey: "children"
    )
}

选项 2

这是基于 Jay 的建议的解决方案。它创建更少的数组(或字典,如果使用命名参数)并且应该更好地扩展更复杂的对象。

migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
    let childrenStrings = oldObject!["children"] as! List<DynamicObject>

    newObject!["children"] = childrenStrings.map {
        let childObject = ChildObject()
        childObject.name = "\([=11=])"
        return childObject
    } as [ChildObject]
}