I am trying to get SwiftData to work with 3 models that are all interconnected. The simplified models and an example View that throws the error are attached below.
Creating a Company and an aircraft works fine, however, when trying to create a flight, the runtime error
Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Illegal attempt to establish a relationship ‘aircrafts’ between objects in different contexts
is thrown. Does anyone know what I might do wrong?
This is the small example to show the problem:
The View with Buttons to trigger the creation of new instances, while the model context is injected in the app struct with .modelContainer(for: [Aircraft.self, Company.self, Flight.self])
:
struct ContentView: View {
@Query var flights: [Flight]
@Query var aircrafts: [Aircraft]
@Query var companies: [Company]
@Environment(.modelContext) private var modelContext
var body: some View {
VStack {
Text("(companies.count) Companies")
Text("(aircrafts.count) Aircrafts")
Text("(flights.count) Flights")
Button("Create Company") {
let company = Company(name: "MyAirline", aircrafts: [], flights: [])
modelContext.insert(company)
}
Button("Create Aircraft") {
guard let company = companies.first else { return }
let aircraft = Aircraft(company: company, flights: [])
modelContext.insert(aircraft)
}
Button("Create Flight") {
guard let company = companies.first,
let aircraft = aircrafts.first
else { return }
let flight = Flight(company: company, aircrafts: [aircraft])
modelContext.insert(flight)
}
Button("Delete everything") {
try? modelContext.delete(model: Flight.self)
try? modelContext.delete(model: Aircraft.self)
try? modelContext.delete(model: Company.self)
}
}
.padding()
}
}
Here are the three models with their relationships:
@Model
final class Company {
@Attribute(.unique) let name: String
@Relationship(deleteRule: .cascade, inverse: Aircraft.company)
var aircrafts: [Aircraft]
@Relationship(deleteRule: .cascade, inverse: Flight.company)
var flights: [Flight]
init(name: String, aircrafts: [Aircraft], flights: [Flight]) {
self.name = name
self.aircrafts = aircrafts
self.flights = flights
}
}
@Model
final class Aircraft {
var company: Company
@Relationship(deleteRule: .nullify, inverse: Flight.aircrafts)
var flights: [Flight]
init(company: Company, flights: [Flight]) {
self.company = company
self.flights = flights
}
}
@Model
final class Flight {
let company: Company
let aircrafts: [Aircraft]
init(company: Company, aircrafts: [Aircraft]) {
self.company = company
self.aircrafts = aircrafts
}
}
2
Answers
You can’t set Relationship vars directly inside init function. Assign a value to them from outside
Also convert aircraft from "let" constant to var :
PS: Change other models like this.
I believe your many-to-many relationship between
Aircraft
andFlight
is the problem. It is an odd setup, at least, to my eye. Certainly a plane can be scheduled for many different flights, but I would expect a flight to use exactly one airplane. The crash is because you have explicitly defined relationships that can’t be achieved.I modified
Flight
to make it 1-many withAircraft
and was able to create flights:But it’s still not possible to delete a flight or aircraft.
When I tested the code, the "Delete everything" button failed with this console message:
I removed the explicit
@Relationship
macros and allowed SwiftData to infer the relationships instead. That worked smoothly, except that you can now be left with zombie flights and aircraft when deleting a Company. So there’s still some work to be done here with figuring out your relationships and delete rules.