Я пытаюсь перенести объект со свойством типа 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 = ""
}

Я использую этот код для выполнения миграции, которая вызывает ошибку:

Встроенные объекты не могут быть созданы напрямую

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)

Вопрос

Как выполнить миграцию, сохранив предыдущие значения?

1
JWK 16 Сен 2021 в 06:09

2 ответа

Лучший ответ

Проще всего создать Dictionary со всеми парами имя / значение свойства каждого дочернего объекта и полностью создать новый List с массивом этих пар.

Но сначала вам нужно извлечь значения String из старого List children. Причина, по которой это необходимо, состоит в том, что Realm не представляет элемент List<String> как фактический String (который является структурой). Мы можем извлечь фактическое значение из поля description:

childrenStrings
    .map { String(describing: $0) }

Когда у вас есть имя, вы можете представить новый ChildObject с помощью Dictionary. Обратите внимание, что вам нужно будет включить все имена и значения свойств в Dictionary. Поскольку у нас есть только одно имя, мы включаем его:

childrenStrings
    .map { String(describing: $0) }
    .map { ["name": $0] }

В вашем сообщении об ошибке сказано:

Встроенные объекты не могут быть созданы напрямую

Однако вы можете создавать свои новые объекты, используя Array из Dictionary, где 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: $0) }
                .map { ["name": $0] }
        ),
        forKey: "children"
    )
}
0
Rob 16 Сен 2021 в 07:51

Подводя итог решениям Роба и Джея соответственно:

Опция 1

Это решение Роба с модификациями.

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

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

Вариант 2

Это решение основано на предложениях Джея. Он создает меньше массивов (или словарей при использовании именованных параметров) и должен лучше масштабироваться с более сложными объектами.

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

    newObject!["children"] = childrenStrings.map {
        let childObject = ChildObject()
        childObject.name = "\($0)"
        return childObject
    } as [ChildObject]
}
0
JWK 16 Сен 2021 в 21:19