After much time and effort I may be able to provide a clue for some people. My simple test app displays a List of parents - you select a parent, and a second List displays it's children. My first attempt worked fine - when I deleted a parent, it's children were cascade deleted (I checked the sqlite3 database to be sure).
Next I tried to programatically select a parent. Now, when I delete that parent, it's children are deleted in my app, but they continue to exist as orphans in the database.
Maybe someone brighter than me can figure out why...
import SwiftUI
import SwiftData
@main
struct Trial_SwiftDatabaseApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([FirstLevel.self, SecondLevel.self])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
@Model
class FirstLevel {
var name: String
@Relationship(deleteRule: .cascade, inverse: \SecondLevel.my1st) var my2nds: [SecondLevel]
init(name: String = "FirstLevel", my2nds: [SecondLevel] = []) {
self.name = name
self.my2nds = my2nds
}
}
@Model
class SecondLevel {
var name: String
var my1st: FirstLevel
init(name: String = "SecondLevel", my1st: FirstLevel) {
self.name = name
self.my1st = my1st
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var firsts: [FirstLevel]
@Query private var seconds: [SecondLevel]
@State private var selected1st: FirstLevel? = nil
@State private var selected2nd: SecondLevel? = nil
var body: some View {
NavigationSplitView {
VStack {
List(firsts, id: \.self, selection: $selected1st) { first in
Text(first.name)
}
.onDeleteCommand() {
deleteFirst()
}
Divider()
.background(.yellow)
HStack {
Button {
addFirstLevel()
} label: { Text("+") }
Spacer()
Text("Firsts: \(firsts.count)")
}
.padding()
}
} detail: {
VStack {
List(filtered2nds, id: \.self, selection: $selected2nd) { second in
Text(second.name)
}
.onDeleteCommand() {
deleteSecond()
}
Divider()
.background(.yellow)
HStack {
Button {
addSecondLevel()
} label: { Text("+") }
Spacer()
Text("Seconds: \(seconds.count)")
}
.padding()
}
}
}
var filtered2nds: [SecondLevel] {
guard let my1st = selected1st else { return [] }
let filtered = seconds.filter { $0.my1st == my1st }
return filtered
}
func addFirstLevel() {
let new1st = FirstLevel(name: "New First")
modelContext.insert(new1st)
//selected1st = new1st <== This stops cascade deletion from working
}
func deleteFirst() {
guard let toDelete = selected1st else { return }
modelContext.delete(toDelete)
}
func addSecondLevel() {
guard let my1st = selected1st else { return }
let new2nd = SecondLevel(name: "New Second", my1st: my1st)
modelContext.insert(new2nd)
}
func deleteSecond() {
guard let my2nd = selected2nd else { return }
modelContext.delete(my2nd)
}
}