You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift Realm迁移失败:迁移后主键identifier存在重复值

Realm数据库迁移触发主键重复报错排查

问题描述

在Swift项目中执行Realm数据库迁移操作时,因主键字段存在重复值触发报错,多次核对新旧数据模型定义、迁移逻辑代码块后仍未定位错误根源。

旧数据模型

class LabelObject: Object, Codable {
    
    @objc dynamic var identifier: String = UUID().uuidString
    
    @objc dynamic var geo_latitude: Double = 0
    @objc dynamic var geo_longitude: Double = 0
    @objc dynamic var geo_radius: Double = 300
    
    @objc dynamic var info_text: String = ""
    @objc dynamic var info_icon: String = ""
    @objc dynamic var info_placemark: String = ""
    
    @objc dynamic var color_red: Float = Float(UIColor.systemBlue.cgColor.components?[0] ?? 0)
    @objc dynamic var color_green: Float = Float(UIColor.systemBlue.cgColor.components?[1] ?? 0)
    @objc dynamic var color_blue: Float = Float(UIColor.systemBlue.cgColor.components?[2] ?? 0)
    @objc dynamic var color_alpha: Float = 1
    
    override class func primaryKey() -> String? {
        return "identifier"
    }

}

新数据模型

class LabelObject: Object {
    
    @Persisted var identifier: String = UUID().uuidString
    
    @Persisted var geo_latitude: Double = 0
    @Persisted var geo_longitude: Double = 0
    @Persisted var geo_radius: Double = 150
    
    @Persisted var info_icon: String = "tag"
    @Persisted var info_text: String = ""
    @Persisted var info_placemark: String = ""
    
    @Persisted var color_red: Double = Double(UIColor.systemBlue.cgColor.components?[0] ?? 0)
    @Persisted var color_green: Double = Double(UIColor.systemBlue.cgColor.components?[1] ?? 0)
    @Persisted var color_blue: Double = Double(UIColor.systemBlue.cgColor.components?[2] ?? 0)
    @Persisted var color_alpha: Double = Double(UIColor.systemBlue.cgColor.components?[3] ?? 1)
    
    override class func primaryKey() -> String? {
        return "identifier"
    }
    
}

原有迁移代码

migration.enumerateObjects(ofType: "LabelObject") { oldObject, _ in
                                
guard let oldObject = oldObject else { return }
                                
let newLabelObject: MigrationObject = migration.create("LabelObject")
                                
newLabelObject["identifier"] = oldObject["identifier"] as? String ?? UUID().uuidString
                                
newLabelObject["geo_latitude"] = oldObject["geo_latitude"] as? Double ?? 0
newLabelObject["geo_longitude"] = oldObject["geo_longitude"] as? Double ?? 0
newLabelObject["geo_radius"] = oldObject["geo_radius"] as? Double ?? georyCurrentConfiguration.preferenceLabelRadius
                                
newLabelObject["info_text"] = oldObject["info_text"] as? String ?? ""
newLabelObject["info_icon"] = oldObject["info_icon"] as? String ?? ""
newLabelObject["info_placemark"] = oldObject["info_placemark"] as? String ?? ""
                                
newLabelObject["color_red"] = newLabelObject["color_red"] as? Float ?? UIColor.systemBlue.cgColor.components?[0] ?? 0
newLabelObject["color_green"] = newLabelObject["color_green"] as? Float ?? UIColor.systemBlue.cgColor.components?[1] ?? 0
newLabelObject["color_blue"] = newLabelObject["color_blue"] as? Float ?? UIColor.systemBlue.cgColor.components?[2] ?? 0
newLabelObject["color_alpha"] = newLabelObject["color_alpha"] as? Float ?? UIColor.systemBlue.cgColor.components?[3] ?? 0
                                    
}

报错信息

Primary key property 'class_LabelObject.identifier' has duplicate values after migration.


根因定位

  • 核心错误:调用migration.enumerateObjects(ofType: "LabelObject")时,Realm会自动为每一条旧数据生成对应的新表映射实例,无需手动调用migration.create("LabelObject")新建对象。手动新建对象后,同一条identifier会对应2条记录:一条是Realm自动映射生成的,一条是手动create的,直接触发主键重复约束报错。
  • 笔误问题:颜色字段赋值逻辑完全错误,所有颜色属性取值时都误写为从newLabelObject读取,而非从oldObject读取,会导致颜色字段数据完全丢失,直接取默认值。
  • 类型不匹配:旧模型颜色字段为Float类型,新模型颜色字段为Double类型,原有代码用as? Float强转不符合新字段类型要求,会导致赋值失败。

修复方案

去掉手动创建对象的逻辑,直接使用枚举回调返回的自动映射生成的newObject做字段调整即可,修正后的迁移代码:

migration.enumerateObjects(ofType: "LabelObject") { oldObject, newObject in
    guard let oldObject = oldObject, let newObject = newObject else { return }
    
    // 同名字段identifier、geo_*、info_text、info_placemark会自动完成类型匹配映射,无需手动赋值
    // 处理颜色字段类型转换:旧值Float -> 新值Double
    newObject["color_red"] = Double(oldObject["color_red"] as? Float ?? Float(UIColor.systemBlue.cgColor.components?[0] ?? 0))
    newObject["color_green"] = Double(oldObject["color_green"] as? Float ?? Float(UIColor.systemBlue.cgColor.components?[1] ?? 0))
    newObject["color_blue"] = Double(oldObject["color_blue"] as? Float ?? Float(UIColor.systemBlue.cgColor.components?[2] ?? 0))
    newObject["color_alpha"] = Double(oldObject["color_alpha"] as? Float ?? 1)
    
    // 按需兼容旧数据空值:旧数据info_icon为空时赋值新默认值"tag",不需要可删除这段
    if (oldObject["info_icon"] as? String ?? "").isEmpty {
        newObject["info_icon"] = "tag"
    }
}

额外排查点

  • 如果修正后仍存在主键重复报错,可在枚举回调中打印所有oldObject["identifier"]的值,排查旧库本身是否存在重复主键(极端情况旧版本写入逻辑异常导致UUID重复),若存在旧数据重复,在迁移时做去重处理,仅保留一条有效记录即可。
  • 从旧版@objc dynamic声明的Realm OC API迁移到10+版本@Persisted声明的Swift原生API时,同名字段会自动完成映射,仅需要手动处理字段重命名、类型变更、默认值兼容逻辑,无需全量手动创建对象,否则必然出现数据重复问题。

内容的提问来源于stack exchange,提问作者EK412

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 06:15:46