Inserting a Model entity with a relationship results in a runtime error.

Hi,

when inserting an entity with a relationship I get the following runtime error: Illegal attempt to establish a relationship 'group' between objects in different contexts [...].

The model looks like this:

@Model
class Person {
  var name: String
  @Relationship(.nullify, inverse: \Group.members) var group: Group

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

@Model
class Group {
  var name: String
  @Relationship(.cascade) public var members: [Person]

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

It can be reproduced using this (contrived) bit of code:

let group = Group(name: "Group A")
ctx.insert(group)
try! ctx.save()

let descriptor = FetchDescriptor<Group>()
let groups = try ctx.fetch(descriptor)
XCTAssertFalse(groups.isEmpty)
XCTAssertEqual(groups.count, 1)
XCTAssertTrue(groups.first?.name == "Group A")

let person = Person(name: "Willy")
person.group = group
ctx.insert(person)
try ctx.save()

(See also full test case below).

Anybody experiencing similar issues? Bug or feature?

Cheers, Michael


Full test case:

import SwiftData
import SwiftUI
import XCTest

// MARK: - Person -

@Model
class Person {
  var name: String
  @Relationship(.nullify, inverse: \Group.members) var group: Group

  init(name: String) {
    self.name = name
  }
}
// MARK: - Group -

@Model
class Group {
  var name: String
  @Relationship(.cascade) public var members: [Person]

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

// MARK: - SD_PrototypingTests -

final class SD_PrototypingTests: XCTestCase {
  var container: ModelContainer!
  var ctx: ModelContext!

  override func setUpWithError() throws {
    let fullSchema = Schema([Person.self,
                             Group.self,])
    let dbCfg = ModelConfiguration(schema: fullSchema)
    container = try ModelContainer(for: fullSchema, dbCfg)

    ctx = ModelContext(container)
    _ = try ctx.delete(model: Group.self)
    _ = try ctx.delete(model: Person.self)
  }

  override func tearDownWithError() throws {
    guard let dbURL = container.configurations.first?.url else {
      XCTFail("Could not find db URL")
      return
    }

    do {
      try FileManager.default.removeItem(at: dbURL)
    } catch {
      XCTFail("Could not delete db: \(error)")
    }
  }

  func testRelAssignemnt_FB12363892() throws {
    let group = Group(name: "Group A")
    ctx.insert(group)
    try! ctx.save()

    let descriptor = FetchDescriptor<Group>()
    let groups = try ctx.fetch(descriptor)
    XCTAssertFalse(groups.isEmpty)
    XCTAssertEqual(groups.count, 1)
    XCTAssertTrue(groups.first?.name == "Group A")

    let person = Person(name: "Willy")
    person.group = group
    ctx.insert(person)
    try ctx.save()
  }
}
@Relationship(.nullify, inverse: \Group.members) var group: Group

group must be optional

Does it make any difference if you change this code:

let person = Person(name: "Willy")
person.group = group
ctx.insert(person)

to this?

let person = Person(name: "Willy")
ctx.insert(person)
person.group = group

That is, to insert both objects into the context before attempting to establish their relationship?

@Purkylin_glow making var optional doesn't work on Xcode 15 Beta 7. That's my case that worked before:

@Model
final class Category {
    ...
    @Relationship(deleteRule: .cascade) var incomes: [Income]
    ...
}

@Model
final class Income {
    ...
    @Relationship(inverse: \Category.incomes) var category: Category?
    ...
}

I'm getting the same error. I am trying to do like a map thing, where a map, has many grid squares, which has many elevation points. If I delete a map, the map, all the grid squares and elevation points are deleted. If I delete a grid square, the grid square and all the elevation points are deleted. oddly it seems to be only the relationship between the last two classes that is the problem. hp_baseboard to be precise. I generally stick to microcontrollers for programming, so this is new.

final class Route {
    //var timestamp: Date
    @Attribute(.unique) var route_name: String
    var route_season: String
    var route_processed: Bool
    var route_coordinates: String
    @Relationship(deleteRule: .cascade, inverse: \Baseboard.baseboard_route) var route_baseboards: [Baseboard] = [] //[UUID]
    
 
    init(route_name: String, route_season: String, route_processed: Bool, route_coordinates: String, route_baseboards: [Baseboard]) {
        //self.timestamp = timestamp
        self.route_name = route_name
        self.route_season = route_season
        self.route_processed = route_processed
        self.route_coordinates = route_coordinates
        self.route_baseboards = route_baseboards
    }
 
    
}

@Model
final class Baseboard {
    //var baseboard_location: (Int, Int) //will have to decide which is which row/col
    var baseboard_column: Int
    var baseboard_row: Int
    var texture: [String]
    var baseboard_processed: Bool
    var baseboard_grid_size: Int
    var baseboard_route: Route?
    @Relationship(deleteRule: .cascade, inverse: \Height_Point.hp_baseboard) var baseboard_heightPoints: [Height_Point] = []
    
    init(/*baseboard_location: (Int, Int)*/baseboard_column: Int, baseboard_row: Int, texture: [String], baseboard_processed: Bool, baseboard_grid_size: Int, baseboard_route: Route, baseboard_heightPoints: [Height_Point]) {
        //self.baseboard_location = baseboard_location
        self.baseboard_column = baseboard_column
        self.baseboard_row = baseboard_column
        self.texture = texture
        self.baseboard_processed = baseboard_processed
        self.baseboard_grid_size = baseboard_grid_size
        self.baseboard_route = baseboard_route
        self.baseboard_heightPoints = baseboard_heightPoints
        
    }
}

@Model
final class Height_Point {
    var hp_baseboard: Baseboard?
    //var hp_location: (Int, Int)
    var hp_column: Int
    var hp_row: Int
    var hp_processed: Bool
    var hp_elevation: Float
    var hp_texture: String
    
    init(hp_baseboard: Baseboard, /*hp_location: (Int, Int),*/ hp_column: Int, hp_row: Int,  hp_processed: Bool, hp_elevation: Float, hp_texture: String) {
        self.hp_baseboard = hp_baseboard
        //self.hp_location = hp_location
        self.hp_column = hp_column
        self.hp_row = hp_row
        self.hp_processed = hp_processed
        self.hp_elevation = hp_elevation
        self.hp_texture = hp_texture
    }
}

The problem is inserting the newItem3

        withAnimation {
            let newItem = Route(route_name: "test route " + UUID().uuidString, route_season: "summer", route_processed: false, route_coordinates: "Somewhere", route_baseboards: []
                                
                
            )
            
            modelContext.insert(newItem)
            let newItem2 = Baseboard(baseboard_column: 0, baseboard_row: 0, texture: ["Grid"], baseboard_processed: false, baseboard_grid_size: 10, baseboard_route: newItem,  baseboard_heightPoints: []
            )
            
            /*modelContext.insert(newItem2)
            newItem2.baseboard_route?.route_baseboards.append(newItem2)*/
            
            let newItem3 = Height_Point(
                hp_baseboard: newItem2,
                hp_column: 0,
                hp_row: 0,
                hp_processed: false,
                hp_elevation: 0,
                hp_texture: "Grid"
            )
            
            modelContext.insert(newItem3)
            /*newItem3.hp_baseboard? .baseboard_heightPoints.append(newItem3)*/
        }
    }

if I don't put something in for baseboard_Route or hp_Baseboard, then it throws up another problem. Yet I'm not sure this is correct either.

Inserting a Model entity with a relationship results in a runtime error.
 
 
Q