Xcode报错: Batch delete failed due to mandatory OTO nullify inverse on TrainRoundModelData/trainTable
Xcode报错: Batch delete failed due to mandatory OTO nullify inverse on TrainRoundModelData/trainTable

Xcode报错: Batch delete failed due to mandatory OTO nullify inverse on TrainRoundModelData/trainTable

问题描述

最近有个朋友通过邮件请教一个问题,在运行下述代码时:

struct ContentView: View {
    
    @Environment(\.modelContext) private var modelContext
    @Query private var trainTables: [TrainTableModelData]
    @Query private var trainRounds: [TrainRoundModelData]
    
    var body: some View {
        VStack {
            Text("TrainTable Count: \(trainTables.count)")
            Text("TrainRound Count: \(trainRounds.count)")
            Button("Add TrainTable with Rounds") {
                let trainTable = TrainTableModelData(name: "Training Plan C")
                modelContext.insert(trainTable)
                
                var tmpTrainRounds = [TrainRoundModelData]()
                
                for i in 1..<10 {
                    let trainRound = TrainRoundModelData(roundIndex: Int32(i), breathSecond: 40, holdSecond: 70)
                    tmpTrainRounds.append(trainRound)
                    
                    trainRound.trainTable = trainTable
                }
            }
            .padding()
            Button("Delete TrainTable") {
                do {
                    try modelContext.delete(model: TrainRoundModelData.self)
                    try modelContext.save()
                } catch {
                    print(error.localizedDescription)
                }
            }
            .padding()
        }
        .padding()
    }
}

当调用modelContext.delete时,Xcode会输出如下报错:

The operation couldn’t be completed. Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on TrainRoundModelData/trainTable

通过查询相关资料,找到《SwiftData: Batch delete failed due to mandatory OTO nullify inverse》这篇文章。这篇文章的答案告知,当delete(model:)用于具有关系的类型时,关系的双方都需要是可选的。

import SwiftData
import SwiftUI

@Model
class TrainTableModelData { // 训练表模型数据
    @Attribute(.unique) var id: UUID
    @Relationship(deleteRule: .cascade) var trainRounds = [TrainRoundModelData]() // 训练轮次,一对多关系,级联
    var name: String
    
    init(id: UUID = UUID(), name: String) {
        self.id = id
        self.name = name
    }
}

@Model
class TrainRoundModelData { // 训练模型数据
    @Attribute(.unique) var id: UUID    // UUID
    @Relationship var trainTable: TrainTableModelData?  // 训练表模型数据
    var roundIndex: Int32
    var breathSecond: Int32
    var holdSecond: Int32
  

    init(id: UUID = UUID(), roundIndex: Int32, breathSecond: Int32, holdSecond: Int32, trainTable: TrainTableModelData? = nil) {
        self.id = id
        self.roundIndex = roundIndex
        self.breathSecond = breathSecond
        self.holdSecond = holdSecond
        self.trainTable = trainTable
    }
}

在这段测试代码中,可以看到 TrainTableModelData 类中的 trainRounds 字段并不是可选的,也就导致了运行delete(model:)时发生了报错。

@Relationship(deleteRule: .cascade) var trainRounds = [TrainRoundModelData]() // 训练轮次,一对多关系,级联

解决方案

解决方案为,将 trainRounds 字段改为可选类型:

@Relationship(deleteRule: .cascade) var trainRounds: [TrainRoundModelData]? // 训练轮次,一对多关系,级联

这样,在执行的时候,就不会有报错产生,并且可以通过delete(model:)成功删除数据。

关于报错的原因,教程中已经指出,SwiftData在删除时尝试nullify(设为nil),因为具有关系的类型不是可选字段,导致设为nil的操作失败,因此导致报错。

因此,设置关系类型的字段为可选字段后,删除操作执行成功。

其他解决方案

解决方案1

因为 TrainRoundModelData 依赖 TrainTableModelData,可以先删除 TrainTableModelData,然后 TrainRoundModelData 会被级联删除。

do {
    try modelContext.delete(model: TrainTableModelData.self) // 先删除 trainTable
    try modelContext.save()
} catch {
    print(error.localizedDescription)
}

删除TrainRoundModelData后,与之关联的TrainRoundModelData一并被删除。

解决方案2

如果想先删除所有 TrainRoundModelData,可以手动删除:

do {
    let trainRounds = try modelContext.fetch(FetchDescriptor<TrainRoundModelData>())
    for trainRound in trainRounds {
        modelContext.delete(trainRound)
    }
    try modelContext.save()
} catch {
    print(error.localizedDescription)
}

这种方式不会触发批量删除的约束问题,因为它是逐条删除。

总结

这里实际还是SwiftData的@Relationship的相关报错,这一问题同时出现在SwiftData同步iCloud中。在同步iCloud的SwiftData对象中,字段需要提供默认值,关系则需要为可选类型,否则无法同步到iCloud。

相关文章

1、SwiftData: Batch delete failed due to mandatory OTO nullify inverse:https://stackoverflow.com/questions/77814385/swiftdata-batch-delete-failed-due-to-mandatory-oto-nullify-inverse

2、SwiftData数据同步到iCloud:https://fangjunyu.com/2024/11/10/swiftdata%e6%95%b0%e6%8d%ae%e5%90%8c%e6%ad%a5%e5%88%b0icloud/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注