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
相关产品推荐
相关产品推荐

