I am using SwiftData and SwiftUI, xcode 15 beta 5. I am trying to establish relationships between entities in DB. The is a User, a Book, and an Ownership between them.
struct asdApp: App {
let container = try! ModelContainer(for: [Ownership.self, User.self, Book.self])
var body: some Scene {
WindowGroup {
ContentView()
.task {
let jon = User()
container.mainContext.insert(jon)
let book = Book()
container.mainContext.insert(book)
let o = Ownership(keeper: jon, book: book) // <-- crash here!!
container.mainContext.insert(o)
}
}
.modelContext(container.mainContext)
}
}
Here are the models, so that you have the full code:
@Model
final class User {
@Attribute(.unique) let id: UUID
init(id: UUID = UUID()) {
self.id = id
}
}
@Model
final class Book {
@Attribute(.unique) let id: UUID
init(id: UUID = UUID()) {
self.id = id
}
}
@Model
final class Ownership {
@Attribute(.unique) var id: UUID
var keeper: User
var book: Book
init(id: UUID = UUID(), keeper: User, book: Book) {
self.id = id
self.keeper = keeper
self.book = book
}
}
Here is the full error:
Thread 1: "Illegal attempt to establish a relationship 'keeper' between objects in different contexts (source = <NSManagedObject: 0x60000212e5d0> (entity: Ownership; id: 0x600000243560 <x-coredata:///Ownership/tFADECEE1-82F7-4071-93B1-259354AE2E2A4>; data: {\n book = nil;\n id = \"6A63596F-66C2-45B3-91C0-FF24FF88B1B5\";\n keeper = nil;\n}) , destination = <NSManagedObject: 0x60000214fed0> (entity: User; id: 0x600000299180 <x-coredata:///User/tFADECEE1-82F7-4071-93B1-259354AE2E2A2>; data: {\n id = \"59B5122C-5CC6-4105-8F11-D48B6D46E63D\";\n}))"
First question: There is only one context, so why the crash?
There is no error if I change the order like this:
var body: some Scene {
WindowGroup {
ContentView()
.task {
let jon = User()
let book = Book()
let o = Ownership(keeper: jon, book: book)
container.mainContext.insert(jon)
container.mainContext.insert(book)
container.mainContext.insert(o)
}
}
.modelContext(container.mainContext)
}
But it doesn't work right, since it creates an extra book. Here is how I found out:
@main
struct asdApp: App {
let container = try! ModelContainer(for: [Ownership.self, User.self, Book.self])
var body: some Scene {
WindowGroup {
ContentView()
.task {
clearAll(modelContainer: container)
let context = container.mainContext
let jon = User()
let book = Book()
let o = Ownership(keeper: jon, book: book)
context.insert(jon)
context.insert(book)
context.insert(o)
do {
for (index, user) in try context.fetch(FetchDescriptor<User>()).enumerated() {
print("\(index) [USER]: id: \(user.id)")
}
for (index, user) in try context.fetch(FetchDescriptor<Book>()).enumerated() {
print("\(index) [BOOK]: id: \(user.id)")
}
for (index, user) in try context.fetch(FetchDescriptor<Ownership>()).enumerated() {
print("\(index) [OWN]: id: \(user.id)")
}
} catch {
fatalError("Error while fetching all entities: \(error)")
}
}
}
.modelContext(container.mainContext)
}
@MainActor
func clearAll(modelContainer: ModelContainer) {
do {
(try modelContainer.mainContext.fetch(FetchDescriptor<User>()))
.forEach {
modelContainer.mainContext.delete($0)
}
(try modelContainer.mainContext.fetch(FetchDescriptor<Book>()))
.forEach {
modelContainer.mainContext.delete($0)
}
(try modelContainer.mainContext.fetch(FetchDescriptor<Ownership>()))
.forEach {
modelContainer.mainContext.delete($0)
}
try modelContainer.mainContext.save()
} catch {
fatalError("Error while fetching all entities: \(error)")
}
}
}
Here is the output:
0 [USER]: id: 14B69B68-1123-4658-9399-7E60E1051A2D
0 [BOOK]: id: 3A52F512-E82F-4574-B2EF-A0B9258EA094
1 [BOOK]: id: 99FC93F5-B81F-4060-A549-869472E53D1D
0 [OWN]: id: 7F1D089A-AF00-4AB0-A17A-275E8448FC20
Second question: Why 2 books, and how did they get different ids, if I only call init once (during debug it only comes there once)? I want 1 book, 1 user and 1 relationship.
Actual example is more complicated, this is the bare minimum I could achieve.
Here is the working solution from @Paulw11 (unfortunately it is just posted as a comment):
Properties in your model that are a relationship to another entity need the @Relationship decorator. You don't really need the Ownership class. A book can just have a keeper
So, just use @Relationship, and you can indeed just store the owner in the book