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

Core Data实体删除异常:删除单个Program的Week会影响其他Program

Core Data实体删除异常问题排查与解决

数据模型结构

Program -->> Weeks -->> Days -->> Exercises

问题现象

在一个Program下创建若干Week实例后,再创建另一个Program并添加Week,删除其中一个Program的Week时,另一个Program中的对应Week也会被删除。Days和Exercises层级存在同样异常。

问题示例

  • Program 1
    1. Week One
    2. Week Two
    3. Week Three
  • Program 2
    1. Week One A
    2. Week Two A
    3. 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)
    }
}

问题原因

核心问题是索引不匹配导致的误删:

  1. WeekViewModel中的weeks数组是全局查询所有WeekEntity的结果,而WeekListView展示的是当前Program关联的Week列表。
  2. 触发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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:10:23