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()
  }
}
Post not yet marked as solved Up vote post of milutz Down vote post of milutz
1.7k views

Replies

@Relationship(.nullify, inverse: \Group.members) var group: Group

group must be optional

  • Thank you!. Do you know if this("... must be optional) is documented somewhere or mentioned in one of the WWDC sessions? Because while it makes it work, it also changes the semantics of the model: With making it an optional "group" is no longer a required relationship, which is what would be correct for my model.

  • not documented and maybe fixed later.

Add a Comment

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?

  • This would not work, since the group attribute of Person is (intentionally) not optional. This will result in a runtime error at the ctx.insert(person) statement. If it would be optional, then it will work.

Add a Comment

@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.

  • My solution was to move hp_baseboard to the end of the regular variables within the height_points class and now the error is gone. I’m using Xcode beta 8. It seems that the variables you are referencing from another class, should be at the end of the regular variables in the class it belongs to and references to other variables at the very end of that class.

    I’m not sure I have the actual referencing order or delete rule correct.

  • @CapFred Thanks! That finally helped!!!

Add a Comment