skip to Main Content

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


  1. You can’t set Relationship vars directly inside init function. Assign a value to them from outside

      let flight = Flight(company: company)
      flight.aircrafts = [aircraft]
      modelContext.insert(flight)
    

    Also convert aircraft from "let" constant to var :

      @Model
      final class Flight {
      let company: Company
      var aircrafts: [Aircraft]
    
      init(company: Company) {
        self.company = company
        self.aircrafts = []
      }
    }
    

    PS: Change other models like this.

    Login or Signup to reply.
  2. I believe your many-to-many relationship between Aircraft and Flight 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 with Aircraft and was able to create flights:

    @Model
    final class Flight {
        let company: Company
        let aircraft: Aircraft
        
        init(company: Company, aircraft: Aircraft) {
            self.company = company
            self.aircraft = aircraft
        }
    }
    

    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:

    error: Unhandled opt lock error from executeBatchDeleteRequest Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on Aircraft/company and userInfo {
    NSExceptionOmitCallstacks = 1;
    NSLocalizedFailureReason = "Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on Aircraft/company";
    "_NSCoreDataOptimisticLockingFailureConflictsKey" =     (
    );
    }
    CoreData: error: Unhandled opt lock error from executeBatchDeleteRequest Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on Aircraft/company and userInfo {
    NSExceptionOmitCallstacks = 1;
    NSLocalizedFailureReason = "Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on Aircraft/company";
    "_NSCoreDataOptimisticLockingFailureConflictsKey" =     (
    );
    }
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search