1

I have the following code:

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Button("add", action: {
                let schoolClass = SchoolClass(name: "test")
                modelContext.insert(schoolClass)
                
                let schoolClassSubject = SchoolClassSubject(schoolClass: schoolClass)
                schoolClass.schoolClassSubjects.append(schoolClassSubject)
            })
            
        }
        .padding()
    }
}

@Model
class SchoolClass {
    var name: String
    var schoolClassSubjects: [SchoolClassSubject] = []
    
    init(name: String) {
        self.name = name
    }
}

@Model
class SchoolClassSubject {
    var schoolClass: SchoolClass
    
    init(schoolClass: SchoolClass) {
        print("test")
        self.schoolClass = schoolClass
    }
}


#Preview {
    ContentView()
}

The line let schoolClassSubject = SchoolClassSubject(schoolClass: schoolClass) breaks with the following exception:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'schoolClass' between objects in different contexts (source = <NSManagedObject: 0x60000214cc30> (entity: SchoolClassSubject; id: 0x60000024ebe0 ; data: {
    schoolClass = nil;
}) , destination = <NSManagedObject: 0x60000214cd70> (entity: SchoolClass; id: 0x600000276740 ; data: {
    name = test;
    schoolClassSubjects =     (
    );
}))'
*** First throw call 

Does anyone understand why? The exception mentions different contexts but the schoolClassSubject is completely new.

Any help is appreciated.

HangarRash
  • 7,314
  • 5
  • 5
  • 32
David Schilling
  • 2,442
  • 3
  • 17
  • 24

1 Answers1

1

This is actually two problems that needs to be resolved.

Firstly the property schoolClassSubjects should be a relationship so change

var schoolClassSubjects: [SchoolClassSubject] = []

to

@Relationship(.nullify, inverse: schoolClass) var schoolClassSubjects: [SchoolClassSubject]

Above I used .nullify as the delete rule but you need to pick one rule that fits your case.

and secondly things needs to be done in the right order.

Because you insert one object into the ModelContext first and then you create the other one and a relationship between them you have a situation where one is in a model context and the other one isn't, this is somewhat incorrectly reported as being in "different contexts".

One way to fix this is to create both objects and the relationships and then insert them into the context and everything will be persisted as expected. I tried some variations on doing this and there seems to be more than one way to make this work but here is what I settled for:

let schoolClass = SchoolClass(name: "test")
let schoolClassSubject = SchoolClassSubject(schoolClass: schoolClass)
modelContext.insert(schoolClass)
modelContext.insert(schoolClassSubject)

Note that we only have to set one end of the relationship now, SwiftData handles the inverse for us.

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • This did unfortunately not help. It gives the same error when i add the `@Relationship` Annotation. Also if i understand it correctly, this Annotation is not really necesarry because SwiftDate can infer the Relationship by himself. – David Schilling Jul 02 '23 at 20:19
  • It turns out that there was two errors here, you _do need_ the @Relationship annotation but you also need to do things in the correct order. If you don't believe me about the Relationship part, turn on debug logging or investigate the created sql tables and you see that they will be wrong. Maybe this is a beta thing and we will not need to use the annotation later, I don't know but now it is needed. – Joakim Danielson Jul 02 '23 at 21:39
  • Thank you. This example works now. The Problem is i made my example simpler for this question. In my real code. `schoolClass` is already saved and i only want to create `schoolClassSubject` with the reference to `schoolClass` – David Schilling Jul 03 '23 at 16:15
  • Thanks for your help. I opened another question with the adjustments i talked about in my comment: https://stackoverflow.com/questions/76606706/swiftdata-modeling-and-saving-non-nullable-relations – David Schilling Jul 03 '23 at 16:43