Core Data实体删除异常:删除单个Program的Week会影响其他Program
Core Data实体删除异常问题排查与解决
数据模型结构
Program -->> Weeks -->> Days -->> Exercises
问题现象
在一个Program下创建若干Week实例后,再创建另一个Program并添加Week,删除其中一个Program的Week时,另一个Program中的对应Week也会被删除。Days和Exercises层级存在同样异常。
问题示例
- Program 1
- Week One
- Week Two
- Week Three
- Program 2
- Week One A
- Week Two A
- Week Three A
删除Program 2中的Week Two A时,Program 1中的Week Two会被同步删除,以此类推。
关联代码
WeekViewModel.swift
import Foundation import CoreData class WeekViewModel: ObservableObject { let manager = PersistenceController.shared @Published var weeks: [WeekEntity] = [] init() { getWeeks() } func getWeeks() { let request = NSFetchRequest<WeekEntity>(entityName: "WeekEntity") let sort = NSSortDescriptor(key: "id", ascending: true) request.sortDescriptors = [sort] do { weeks = try manager.container.viewContext.fetch(request) } catch let error { print("Error fetching weeks! \(error)") } } func addWeek(name: String, program: ProgramEntity) { let newWeek = WeekEntity(context: manager.container.viewContext) newWeek.name = name newWeek.programs = program save() } func deleteWeeks(at offsets: IndexSet) { for index in offsets { let week = weeks[index] manager.context.delete(week) } save() getWeeks() } func save() { manager.save() } }
WeekListView.swift
import SwiftUI import CoreData struct WeekListView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var weekViewModel: WeekViewModel var program: ProgramEntity var body: some View { Group { if let mulWeeks = program.multipleWeeks?.array as? [WeekEntity] { ForEach(mulWeeks, id: \.self) { week in ZStack { Text(week.name ?? "") .font(.system(size: 40, weight: .light, design:.default)) .foregroundColor(Color("lightblue")) .padding() .frame(width: 350, height: 60, alignment: .leading) NavigationLink(destination: DayView(week: week)) { EmptyView() } .opacity(0.0) } } .onDelete(perform: weekViewModel.deleteWeeks) } } } } struct WeekListView_Previews: PreviewProvider { static var previews: some View { let context = PersistenceController.shared.container.viewContext let program = ProgramEntity(context: context) program.name = "Program 2" return WeekListView(program: program) .previewLayout(.sizeThatFits) .environmentObject(WeekViewModel()) } }
参考代码(Programs模块)
ProgramsViewModel.swift
import Foundation import CoreData class ProgramsViewModel: ObservableObject { let manager = PersistenceController.shared @Published var programs: [ProgramEntity] = [] init() { getPrograms() } func getPrograms() { let request = NSFetchRequest<ProgramEntity>(entityName: "ProgramEntity") let sort = NSSortDescriptor(key: "id", ascending: true) request.sortDescriptors = [sort] do { programs = try manager.container.viewContext.fetch(request) } catch let error { print("Error fetching programs! \(error)") } } func addProgram(name: String, weeks: Int64, days: Int64) { let newProgram = ProgramEntity(context: manager.container.viewContext) newProgram.name = name newProgram.weeks = weeks newProgram.days = days save() } func deletePrograms(at offsets: IndexSet) { for index in offsets { let program = programs[index] manager.context.delete(program) } save() getPrograms() } func moveProgram(from: IndexSet, to: Int) { programs.move(fromOffsets: from, toOffset: to) save() } func save() { manager.save() } func addWeekProgram(week: WeekEntity) { let newWeek = ProgramEntity(context: manager.container.viewContext) newWeek.addToMultipleWeeks(week) } }
ProgramsListView.swift
import SwiftUI import CoreData struct ProgramListView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var programsViewModel: ProgramsViewModel var body: some View { workingVStack } } struct ProgramListView_Previews: PreviewProvider { static var previews: some View { ProgramListView() .previewLayout(.sizeThatFits) .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) .environmentObject(ProgramsViewModel()) } } extension ProgramListView { private var workingVStack: some View { ForEach(programsViewModel.programs, id: \.self) { program in ZStack { VStack(alignment: .leading) { HStack { Text(program.name ?? "") .font(.system(size: 40, weight: .light, design: .default)) .foregroundColor(Color("lightblue")) } HStack { Text("Weeks:") .foregroundColor(.gray) Text("\(program.weeks)") Spacer() Text("Days:") .foregroundColor(.gray) Text("\(program.days)") } .frame(width: 300) } NavigationLink(destination: WeekView(program: program)) { EmptyView() } .opacity(0.0) } } .onDelete(perform: programsViewModel.deletePrograms) .onMove(perform: programsViewModel.moveProgram) } }
问题原因
核心问题是索引不匹配导致的误删:
WeekViewModel中的weeks数组是全局查询所有WeekEntity的结果,而WeekListView展示的是当前Program关联的Week列表。- 触发
onDelete时传入的offsets是当前Program本地Week列表的索引,但deleteWeeks方法用这个索引去取全局weeks数组中的元素,导致删除了错误的Week实例。
解决方案
方案1:直接在视图中处理删除(推荐轻量场景)
修改WeekListView的onDelete逻辑,直接操作当前Program关联的Week列表,不依赖全局数组:
// WeekListView.swift 修改Group部分 Group { if let mulWeeks = program.multipleWeeks?.array as? [WeekEntity] { ForEach(mulWeeks, id: \.self) { week in // 原有视图代码不变 } .onDelete(perform: { offsets in guard let targetWeeks = program.multipleWeeks?.array as? [WeekEntity] else { return } for index in offsets { let weekToDelete = targetWeeks[index] viewContext.delete(weekToDelete) } do { try viewContext.save() } catch { print("Delete week error: \(error)") } }) } }
方案2:让ViewModel关联当前Program(适合复杂场景)
重构WeekViewModel,添加currentProgram属性,确保查询和操作的都是当前Program下的Week:
// WeekViewModel.swift 修改代码 class WeekViewModel: ObservableObject { let manager = PersistenceController.shared @Published var weeks: [WeekEntity] = [] var currentProgram: ProgramEntity? init(program: ProgramEntity? = nil) { currentProgram = program getWeeks() } func getWeeks() { let request = NSFetchRequest<WeekEntity>(entityName: "WeekEntity") // 添加谓词过滤当前Program的Week if let program = currentProgram { request.predicate = NSPredicate(format: "programs == %@", program) } let sort = NSSortDescriptor(key: "id", ascending: true) request.sortDescriptors = [sort] do { weeks = try manager.container.viewContext.fetch(request) } catch let error { print("Error fetching weeks! \(error)") } } func deleteWeeks(at offsets: IndexSet) { for index in offsets { let week = weeks[index] manager.context.delete(week) } save() getWeeks() } // 其他方法不变 }
修改WeekListView初始化逻辑,传入当前Program:
// WeekListView.swift 修改代码 struct WeekListView: View { @Environment(\.managedObjectContext) private var viewContext @StateObject var weekViewModel: WeekViewModel init(program: ProgramEntity) { _weekViewModel = StateObject(wrappedValue: WeekViewModel(program: program)) } // body代码不变 } // 修改预览 struct WeekListView_Previews: PreviewProvider { static var previews: some View { let context = PersistenceController.shared.container.viewContext let program = ProgramEntity(context: context) program.name = "Program 2" return WeekListView(program: program) .previewLayout(.sizeThatFits) } }
额外检查
确认Core Data模型中Program和Week的关系为一对多,Week的反向关系为一对一,删除规则保持默认即可(无需级联删除,除非有明确需求)。
内容的提问来源于stack exchange,提问作者marsioe
相关产品推荐
相关产品推荐

