Realm iOS - 如何更新列表?

3

我在iOS上使用Codable和Realm,并发现在更新已保存在Realm中的现有记录时遇到了问题。数据由项目列表组成,每个项目都有一个类别列表,每个类别都有一个描述。

这是我的Realm Codable模型的样子

class Items: Object, Decodable {

    @objc dynamic var id: String = ""
    @objc dynamic var name: String = ""
    let categories =  List<Categories>()
    @objc dynamic var desc: Desc?

    override static func primaryKey() -> String? {
        return "id"
    }

    var hasSubCategories: Bool {
        if self.categories.count > 0 {
            return true
        }
        return false
    }

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case desc
        case categories
    }

    required init() {
        super.init()
    }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(String.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        desc = try (container.decodeIfPresent(Desc, forKey: .desc))
        let categoriesList = try container.decodeIfPresent(List<Categories>.self, forKey: .categories) ?? List<Categories>()
        categories.append(objectsIn: categoriesList)
        super.init()
    }
}

class Categories: Object, Decodable {

    @objc dynamic var id: String = ""
    @objc dynamic var name: String = ""
    @objc dynamic var desc: Desc?
    let categories =  List<Categories>()

    override static func primaryKey() -> String? {
        return "id"
    }

    var hasSubCategories: Bool {
        if categories.count > 0 {
            return true
        }
        return false
    }

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case categories
   }

    required init() {
        super.init()
    }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(String.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        desc = try (container.decodeIfPresent(Desc, forKey: . desc))
        let categoriesList = try container.decodeIfPresent(List<Categories>.self, forKey: .categories) ?? List<Categories>()
        categories.append(objectsIn: categoriesList)
        super.init()
    }
}

我使用以下代码将从API接收到的新项目添加进去:

let realm = try? Realm()

 for item in ItemsFromAPI {

do {
     try realm?.write {
         realm?.add(item)
       }
    }
}

当从API接收到已保存在数据库中的数据时,我需要更新它。据我理解,应该使用主键实现更新。
realm?.add(Item, update: .modified)

将会更新具有相同主键的现有记录。类为Item的对象属性已被更新,但是categories类型为List<>的对象没有被更新。

我尝试获取数据库中保存的现有categories列表,并通过调用savedItem.categories.removeAll()改变与savedItem关联的categories对象,并添加新的categories,方法是savedItem.categories.append(objectsIn: itemFromAPI.categories)。当我保存这些更改时:

realm?.add(Item, update: .modified)

Realm崩溃并抛出异常 - “RLMException原因:尝试创建一个已经存在主键值的对象”

我尝试着移除这个类别,使用方法是

realm?.delete(Category)

然后添加与项目相关的修改后的类别,目前已经运行正常。

我有多个级别的嵌套类别,通过再次获取它们,删除并重新添加它们来更新它们,这证明是一种痛苦的方式。在realm中,更新记录是这样工作的吗?

我能否通过简单地将新对象分配给项目并使用来更新与项目相关的嵌套类别

realm?.add(Item, update: .modified) 

如何更新记录,就像添加新项目时一样?

非常感谢您的回复。


我认为你在这里询问的是如何使Realm List属性可编码,对吗?如果是这样,在SO上已经有人问过并回答了几次。请参见此问题和答案以及此问题。查看一下它们是否有所帮助,因为List对象不能直接编码或解码。 - Jay
不是。我的问题是如何使用realm?.add(Item, update: .modified)函数更新Realm列表属性。它似乎无法更新列表属性。我应该获取现有的数据库条目,然后删除条目,创建具有相同主键的新条目以进行更新,还是可以简单地构建包含新值的持有列表的主对象,并使用设置更新标志的realm add函数更新它们? - Prabhu
我想我有一个答案给你。如果需要澄清,请告诉我。 - Jay
1个回答

0

根据评论,问题是

我的问题是如何使用realm?.add(Item, update: .modified)函数更新Realm列表属性。

为了更清晰地表述问题,让我重新陈述一下问题

如何更新存储在另一个对象的List属性中的对象,其中列表中存储的对象具有主键

简短的答案是:List对象保存对该列表中其他对象的引用。因为它包含引用,所以只需要更新它引用的对象,而不需要更新List本身

长答案通过示例来说明:假设有一个人,他有一个Dogs的List属性

class PersonClass: Object {
   @objc dynamic var person_id = UUID().uuidString
   let dogs = List<DogClass>()

    override static func primaryKey() -> String? {
        return "person_id"
    }
}

class DogClass: Object {
   @objc dynamic var dog_id = NSUUID().uuidString
   @objc dynamic var dog_name = ""

   override static func primaryKey() -> String? {
        return "dog_id"
    }
}

关键在于 List 对象包含对其内部存储对象的引用,而不是“持有”这些实际对象。如果列表中的对象属性发生变化,则列表本身不受影响,并仍然引用该已更改的对象。

假设我们的人有两只狗。

let person = PersonClass()

let dog0 = DogClass()
dog0.dog_name = "Spot"
let dog1 = DogClass()
dog1.dog_name = "Rover"

person.dogs.append(objectsIn: [dog0, dog1])

try! realm.write {
    realm.add(person)
}

现在这个人有两只狗:Spot和Rover。

假设我们想把Rover的名字改成Lassie。无需更改或更新人员列表 - 只需在realm中更新狗的名称属性,确保使用相同的dog_id,并且该更改将反映在人员列表中。

let updatedDog = DogClass()
updatedDog.dog_id = "the dogs primary key"
updatedDog.dog_name = "Lassie"

try! realm.write {
   realm.add(updatedDog, update: .modified)
}

感谢Jay向我解释这个问题。由于我正在使用Swift Codable与Realm,我有将所有内容解析和合并到包含列表的父类对象中的优势。我想知道是否可以像插入新对象到Realm DB那样,使用这种方法来更新父级以及与父级相关联的子级列表。根据您的示例,在狗的信息更改时,我获得完整的人员记录和狗数据。使用您的方法,我将不得不更新人员数据并获取相关的狗列表,并循环遍历各个狗进行更新。 - Prabhu
@Prabhu 很多时候,Codable 带来的麻烦比它所值得的要多得多。一个很好的使用案例是,如果你将纯 JSON 导入到 Swift 对象中,然而,Realm 不是 Swift - 它由 ObjC 代码和对象支持,并且你可能会发现最好使用初始化器代码创建对象,以映射对象属性。 - Jay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接