swiftswiftdataswift-data-relationship

SwiftData does not retrieve my inverse one-to-many Relationship


Here is my models:

import SwiftData

@Model
final public class FirstModel {
    let name: String

    @Relationship(deleteRule: .cascade, inverse: \SecondModel.parent) var children = [SecondModel]()

    init(name: String) {
        self.name = name
    }
}

@Model
final public class SecondModel {
    let parent: FirstModel
    let name: String

    @Relationship(deleteRule: .cascade, inverse: \ThirdModel.parent) var children = [ThirdModel]()

    init(name: String, parent: FirstModel) {
        self.name = name
        self.parent = parent
    }
}

@Model
final public class ThirdModel {
    let parent: SecondModel
    let name: String

    init(name: String, parent: SecondModel) {
        self.name = name
        self.parent = parent
    }
}

Then I create my model entries:

let schema = Schema([
    FirstModel.self,
    SecondModel.self,
    ThirdModel.self
])

let container = try ModelContainer(for: schema)
let context = ModelContext(container)

let firstModel = FirstModel(name: "my first model")
let secondModel = SecondModel(name: "my second model", parent: firstModel)
let thirdModel = ThirdModel(name: "my third model", parent: secondModel)

context.insert(firstModel)
context.insert(secondModel)
context.insert(thirdModel)

try context.save()

I want to retrieve the children from my models:

print("-- Fetch Third Model")
let thirdFetchDescriptor: FetchDescriptor<ThirdModel> = FetchDescriptor<ThirdModel>(predicate: #Predicate { $0.name == "my third model" })
let thirdModels = try context.fetch(thirdFetchDescriptor)
for entry in thirdModels {
    print(">>> \(entry) - \(entry.parent) - \(entry.parent.parent)")
}

print("-- Fetch First Model")
let firstFetchDescriptor: FetchDescriptor<FirstModel> = FetchDescriptor<FirstModel>(predicate: #Predicate { $0.name == "my first model" })
let firstModels = try context.fetch(firstFetchDescriptor)
for entry in firstModels {
    print(">>> \(entry) - \(entry.children)")
    for child in entry.children {
        print("\t>>> \(child) - \(child.children)")
    }
}

... But it does not seem to work:

-- Fetch Third Model
>>> ThirdModel - SecondModel - FirstModel

-- Fetch First Model
>>> FirstModel - []

What I would expect to see:

-- Fetch First Model
>>> FirstModel - [SecondModel]
    >>> SecondModel - [ThirdModel]

I am not sure what I am doing wrong or missing...


Solution

  • The following worked for me:

    Change the inverse properties to optional vars

    var parent: FirstModel?
    
    var parent: SecondModel?
    

    and you should only insert one of the models. This will cause all the related models to be inserted.

    context.insert(firstModel)
    //context.insert(secondModel)
    //context.insert(thirdModel)
    

    Full code:

    struct ContentView: View {
        
        var body: some View {
            Text("Foo")
                .onAppear {
                    do {
                        let schema = Schema([
                            FirstModel.self,
                            SecondModel.self,
                            ThirdModel.self
                        ])
    
                        let container = try ModelContainer(for: schema, configurations: .init(isStoredInMemoryOnly: true))
                        let context = ModelContext(container)
    
                        let firstModel = FirstModel(name: "my first model")
                        let secondModel = SecondModel(name: "my second model", parent: firstModel)
                        let thirdModel = ThirdModel(name: "my third model", parent: secondModel)
    
                        context.insert(firstModel)
    
                        try context.save()
                        
                        print("-- Fetch Third Model")
                        let thirdFetchDescriptor: FetchDescriptor<ThirdModel> = FetchDescriptor<ThirdModel>(predicate: #Predicate { $0.name == "my third model" })
                        let thirdModels = try context.fetch(thirdFetchDescriptor)
                        for entry in thirdModels {
                            print(">>> \(entry) - \(entry.parent!) - \(entry.parent!.parent!)")
                        }
    
                        print("-- Fetch First Model")
                        let firstFetchDescriptor: FetchDescriptor<FirstModel> = FetchDescriptor<FirstModel>(predicate: #Predicate { $0.name == "my first model" })
                        let firstModels = try context.fetch(firstFetchDescriptor)
                        for entry in firstModels {
                            print(">>> \(entry) - \(entry.children)")
                            for child in entry.children {
                                print("\t>>> \(child) - \(child.children)")
                            }
                        }
                    } catch {
                        print(error)
                    }
                }
        }
    }
    @Model
    final public class FirstModel {
        let name: String
    
        @Relationship(deleteRule: .cascade, inverse: \SecondModel.parent) var children = [SecondModel]()
    
        init(name: String) {
            self.name = name
        }
    }
    
    @Model
    final public class SecondModel {
        var parent: FirstModel?
        let name: String
    
        @Relationship(deleteRule: .cascade, inverse: \ThirdModel.parent) var children = [ThirdModel]()
    
        init(name: String, parent: FirstModel) {
            self.name = name
            self.parent = parent
        }
    }
    
    @Model
    final public class ThirdModel {
        var parent: SecondModel?
        let name: String
    
        init(name: String, parent: SecondModel) {
            self.name = name
            self.parent = parent
        }
    }