Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swiftdata cascade deletion rule not working?

Tags:

swiftdata

I have the following code :

@Model
final class Parent {
    
    @Relationship(deleteRule: .cascade)
    var children: [Child]

    init() {
        children = []
    }
}


@Model
final class Child {
    
    @Relationship(deleteRule: .nullify)
    var parent: Parent?

    init() {
        parent = nil
    }
}





struct ContentView: View {
    @Environment(\.modelContext) private var context
    
    var body: some View {
        VStack {
            View1()
            Divider()
            View2()
            Divider()
            View3()
            Divider()
            View4()
        }
    }
    
    func View1() -> some View {
        // Works perfectly
        let parentFetch     = FetchDescriptor<Parent>()
        let childFetch      = FetchDescriptor<Child>()
        return Text("1 - Parents: \(try! context.fetchCount(parentFetch)) - Children: \(try! context.fetchCount(childFetch))")
    }
    
    func View2() -> some View {
        // Works perfectly
        let parentFetch     = FetchDescriptor<Parent>()
        let childFetch      = FetchDescriptor<Child>()
        
        let parent  = Parent()
        context.insert(parent)
        let child1  = Child()
        child1.parent   = parent
        let child2  = Child()
        child2.parent   = parent
        let child3  = Child()
        child3.parent   = parent
        
        return VStack {
            Text("2 - Parents: \(try! context.fetchCount(parentFetch)) - Children: \(try! context.fetchCount(childFetch))")
            Text("2 - Parent has: \(parent.children.count) children")
        }
    }
    
    func View3() -> some View {
        // Works perfectly
        let parentFetch     = FetchDescriptor<Parent>()
        let childFetch      = FetchDescriptor<Child>()
        
        let children        = try! context.fetch(childFetch)
        context.delete(children[0])
        try! context.save()
        
        let parent          = (try! context.fetch(parentFetch))[0]
        
        return VStack {
            Text("3 - Parents: \(try! context.fetchCount(parentFetch)) - Children: \(try! context.fetchCount(childFetch))")
            Text("3 - Parent has: \(parent.children.count) children")
        }
    }
    
    func View4() -> some View {
        // Just doesn't work
        let parentFetch     = FetchDescriptor<Parent>()
        let childFetch      = FetchDescriptor<Child>()
        
        let parent          = (try! context.fetch(parentFetch))[0]
        context.delete(parent)
        // Children won't be deleted !!!
        try! context.save()

        return VStack {
            Text("4 - Parents: \(try! context.fetchCount(parentFetch)) - Children: \(try! context.fetchCount(childFetch))")
        }
    }
}

When deleting the parent in View4(), I expect the children to be deleted too, as a consequence of the .cascade rule. But that's not the case, as shown here:

enter image description here

I have tried with and without specifying the inverse, with or with optional...

What's the trick, is that a known bug ? I found no trace of it.

like image 580
AirXygène Avatar asked Mar 01 '26 13:03

AirXygène


1 Answers

OK, since it appears to be a bug in Swiftdata (I wonder how they could miss this one, but it seems Swiftdata is not mature, as I also found also two other obvious bugs), I propose the following work-around:

extension ModelContext {

    ///
    /// TODO:  BE REMOVED WHEN `.cascade` is fixed
    ///
    public func delete(parent: Parent) {
        for child in parent.children {
            self.delete(child)
        }
        self.delete(parent)
    }
}

Not ideal, as not API transparent, and also, you need to know you're deleting a parent.

But I'll accept any better workaround.

like image 153
AirXygène Avatar answered Mar 03 '26 08:03

AirXygène